5/10/2016

Design: Overloading vs. overriding vs. template

Printing shape information requires a function that interacts with various types of shape: circle, polygon, and so on. One way to do this is to have overloaded versions of a function that accepts different type of arguments:

void printShapeInfo(Polygon const &);
void printShapeInfo(Circle const &);

Overloading creates a dependency per version of printShapeInfo to its argument type, but the arguments remain independent from each other. The main drawback is when you add a new shape, for example, ellipse, you also need to add another overload:

void printShapeInfo(Ellipse const &);

And so on for each new type of shape you add.

There are two alternative ways to implement printShapeInfo.

Create a class called Shape. In Shape, declare a pure virtual function description(). Derive Polygon and Circle from it, overriding the inherited member function description().

class Shape {
public:
    virtual std::string description() const =0;
};
class ConvexPolygon : public Shape {

string description() const {…}

};
class Circle : public Shape {

string description() const {…}

};
Write a polymorphic printShapeInfo as follows:

std::string printShapeInfo(Shape const & s){
    return s.description();
}

The type of actual argument the parameter s refers to is determined when the call to printShapeInfo takes place. This late binding or dynamic binding eliminate the need to overload printShapeInfo for any new shape along as it derives from shape (or any of its derived classes). The solution ties Circle and Polygon to Shape: they become siblings by deriving from the same base class.

Lastly, you can avoid the overloading hassle by making printShapeInfo a template function:

template <class T>
std::string printShapeInfo(T const & s){
    return s.description();
}

Of course, you must ensure an argument passed to it has the member function description(); an argument without is flagged as compilation error.

Template keeps Polygon, Circle, etc. independent as in the case of overloading.

4/22/2016

Delegation from ConvexPolygon to Vector

In the convex polygon problem, we are asked to compute the perimeter of a polygon. We have two obvious options.

Option 1: Perimeter of a convex polygon as a C function 

A convex polygon is an argument passed to this function, which calls the convex polygon’s member function vertex(int) to get vertices to compute distance. The vertex, in turn, is a Vector. So the perimeter function computes distance by getting the components of the two vectors, and then does the square root of sums of square of difference in corresponding components. While all this is going on, perimeter needs to check that the two vectors are of the same dimension.

The perimeter function we just described is busy, nosy, and doing all the work.

Option 2: Perimeter of a convex polygon as a member function 

We can do better. With classes ConvexPolygon and Vector, what perimeter needs to do has been done to a large degree. So, give the perimeter function to Polygon to have direct accesses to the vertices. Have the vector representing two adjacent vertices (i.e., the difference of the two vectors representing the adjacent vertices) compute its own length, checking dimensionality, etc. So, in computing perimeter, ConvexPolygon delegates much of the computation to Vector:


 

The reason that ConvexPolygon is able to delegate computing work to Vector is partly because Vector encapsulates the data and operations Polygon needs. In this case, the operations include Vector's overloaded operator - and the length function; see lines 22 and 23 above. Thus, encapsulation is a foundation for delegation: think about representing a vector with array on numbers and functions all scattered around; it would have been impossible for ConvexPolygon to delegate the computation. 

This delegation is one-way collaboration because Vector does not delegate computing tasks to ConvexPolygon. In contrast, collaboration is two-way or more; more on this later.