ProgrammingStylesOrParadigms

When we design and implement a program, we aim for a consistent style. C++ supports four major styles that can be considered fundamental:

Procedural programming
Data abstraction
Object-oriented programming
Generic programming

These are sometimes (somewhat pompously) called “programming paradigms.”There are many more “paradigms,” such as functional programming, logic programming, rule-based programming, constraints-based programming, and aspect oriented programming.

Procedural programming:

the idea of composing a program out of functions operating on arguments. Examples are libraries of mathematical functions, such as sqrt() and cos(). C++ supports this style of programming through the notion of functions. The ability to choose to pass arguments by value, by reference, and by const reference can be most valuable. Often, data is organized into data structures represented as structs. Explicit abstraction mechanisms (such as private data members or member functions of a class) are not used. Note that this style of programming - and functions - is an integral part of every other style.

Data abstraction:

the idea of first providing a set of types suitable for an application area and then writing the program using those. Matrices provide a classic example. Explicit data hiding (e.g., the use of private data members of a class) is heavily used. The standard string and vector are popular examples, which show the strong relationship between data abstraction and parameterization as used by generic programming. This is called “abstraction” because a type is used through an interface, rather than by directly accessing its implementation.

Object-oriented programming:

the idea of organizing types into hierarchies to express their relationships directly in code. The classic example is the Shape hierarchy. This is obviously valuable when the types really have fundamental hierarchical relationships. However, there has been a strong tendency to overuse; that is, people built hierarchies of types that do not belong together for fundamental reasons. When people derive, ask why. What is being expressed? How does the base/derived distinction help me in this particular case?

Generic programming:

the idea of taking concrete algorithms and “lifting” them to a higher level of abstraction by adding parameters to express what can be varied without changing the essence of an algorithm. The high() example below is a simple example of lifting. The find() and sort() algorithms from the STL are classic algorithms expressed in very general forms using generic programming.

template<typename Iterator>
Iterator high(Iterator first, Iterator last)
// return an iterator to the element in [first:last) that has the highest value
{
    Iterator high = first;
    for (Iterator p = first; p!=last; ++p)
        if (*high<*p) high = p;
    return high;
}

All together now! Often, people talk about programming styles (“paradigms”) as if they were simple disjointed alternatives: either you use generic programming or you use object-oriented programming. If your aim is to express solutions to problems in the best possible way, you will use a combination of styles. By “best,” we mean easy to read, easy to write, easy to maintain, and sufficiently efficient. Consider an example: the classic “Shape example” originated with Simula and is usually seen as an example of object-oriented programming. A first solution might look like this:

void draw_all(vector<Shape*>& v)
{
    for(int i = 0; i<v.size(); ++i) 
        v[i]->draw();
}

This does indeed look “rather object-oriented.” It critically relies on a class hierarchy and on the virtual function call finding the right draw() function for every given Shape; that is, for a Circle, it calls Circle::draw() and for an Open_polyline, it calls Open_polyline::draw(). But the vector is basically a generic programming construct: it relies on a parameter (the element type) that is resolved at compile time. We could emphasize that by using a simple standard library algorithm to express the iteration over all elements:

void draw_all(vector<Shape*>& v)
{
    for_each(v.begin(),v.end(),mem_fun(&Shape::draw));
}

The third argument of for_each() is a function to be called for each element of the sequence specified by the first two arguments. Now, that third function call is assumed to be an ordinary function (or a function object) called using the f(x) syntax, rather than a member function called by the p->f() syntax. So, we use the standard library function mem_fun() to say that we really want to call a member function (the virtual function Shape::draw()). The point is that for_each() and mem_fun(), being templates, really aren’t very “OO-like”; they clearly belong to what we usually consider generic programming. More interesting still, mem_fun() is a freestanding (template) function returning a class object. In other words, it can easily be classified as plain data abstraction (no inheritance) or even procedural programming (no data hiding). So, we could claim that this one line of code uses key aspects of all of the four fundamental styles supported by C++.

But why would we write the second version of the “draw all Shapes” example? It fundamentally does the same thing as the first version; it even takes a few more characters to write it in that way! We could argue that expressing the loop using for_each() is “more obvious and less error-prone” than writing out the forloop, but for many that’s not a terribly convincing argument. A better one is that “for_each() says what is to be done (iterate over a sequence) rather than how it is to be done.” However, for most people the convincing argument is simply that “it’s useful”: it points the way to a generalization (in the best generic programming tradition) that allows us to solve more problems. Why are the shapes in a vector?

Why not a list? Why not a general sequence? So we can write a third (and more general) version:

template<class Iter> 
void draw_all(Iter b, Iter e)
{
for_each(b,e,mem_fun(&Shape::draw));
}
This will now work for all kinds of sequences of shapes. In particular, we can even call it for the elements of an array of Shapes:
Point p {0,100};
Point p2 {50,50};
Shape* a[] = { new Circle(p,50), new Triangle(p,p2,Point(25,25)) };
draw_all(a,a+2);

We could also provide a version that is simpler to use by restricting it to work on containers:

template<class Cont> 
void draw_all(Cont& c)
{
    for (auto& p : c) 
        p->draw();
}

Or even, using C++14 concepts:

void draw_all(Container& c)
{
    for (auto& p : c) 
        p->draw();
}

The point is still that this code is clearly object-oriented, generic, and very like ordinary procedural code. It relies on data abstraction in its class hierarchy and the implementation of the individual containers. For lack of a better term, programming using the most appropriate mix of styles has been called multi-paradigm programming.

However, I have come to think of this as simply programming: the “paradigms” primarily reflect a restricted view of how problems can be solved and weaknesses in the programming languages we use to express our solutions. I predict a bright future for programming with significant improvements in technique, programming languages, and support tools.

ref

Programming Principles and Practice Using C++ 2nd Edition.2014.5 22th chapter

本页共84段,7693个字符,7839 Byte(字节)