Search code examples
oopinheritancesingle-responsibility-principle

Inheritance and responsibility


When I read about inheritance I'm always confused about a certain example.

Usually there's an example similar to the example below.

class Shape
{
public:
    Shape() {}
    virtual ~Shape  () {}
    virtual void Draw() = 0;
};

class Cube : public Shape
{
public:
   Cube(){}
   ~Cube(){}
   virtual void Draw();
};

Shape* newCube = new Cube();
newCube->Draw(); 

My question is, why is it the Shape's responsibility to draw itself? Shouldn't it be the responsibility of a renderer class to know how to draw a shape and instead provide the shape to the renderer? What if we wanted to record changes in dimensions? Etc? Would we have a method for each of these different tasks inside of Shape?

Seeing numerous examples like these sometimes make me wonder about my ability to assign responsibilities to classes. Is there something I'm not understanding about classes only having one responsibility?


Solution

  • A Shape should have no knowledge about how it's drawn. The larger the project being designed, the more critical this decision is.

    For me, it all boils down to circular dependencies which, in all but the edgiest of cases, causes nothing but headaches.

    A basic tenet of Model View Controller is that the things that you do (the verbs, or "view") are clearly separated from the things (the nouns, or "controller") that are being manipulated or analyzed: Separation of presentation and logic. The "model" is the middle man.

    It's also the single responsibility principle: "...every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class"

    The reason behind it is this: A circular dependency means that any change to anything, affects everything.

    Another (edited-for-brevity) quote from the single responsibility principle: "A class or module should have one, and only one, reason to change. The single responsibility principle says that substantive and cosmetic aspects of a problem are really two separate responsibilities, and should therefore be in separate classes or modules. It would be a bad design to couple two things that change for different reasons at different times." (emphasis mine)

    Finally, the concept of separation of concerns: "The goal is to design systems so that functions can be optimized independently of other functions, so that failure of one function does not cause other functions to fail, and in general to make it easier to understand, design and manage complex interdependent systems." (emphasis mine)


    This is not just a programming-design issue.

    Consider the development of a website, where the "content" team has to place their words and formatting and colors and pictures, very gently around some scripting (as created by the "development" team), just so, or everything breaks. The content team wishes to see no scripting at all--they don't want to have to learn how to program, just so they can change some words or tweak an image. And the development team doesn't want to have to worry that every minor visual change, made by people who don't know how to code, has the potential to break their stuff.

    I think about this concept every day when working on my own projects. When two source files import each other, a change in either one requires that both be re-compiled--and at the same time. With larger projects, this could mean a trivial change requires the re-compilation of hundreds or thousands of classes. In the three major projects I'm currently involved in, in which there are about a thousand different source-code files, there is exactly one circular dependency of this kind.

    Whether teams in a buisiness, source-code files, or designing programming objects, circular dependencies is something I recommend avoiding unless absolutely necessary.


    So, at least, I would not put the draw function in Shape. Although very dependant on the type and size of the project being designed, the rendering could be done by a RenderingUtils class, containing nothing but public static functions that do the bulk of the work.

    Were the project a moderately large one, I would go further and create a Renderable interface as the model layer. A Shape implements Renderable, and would therefore not know or care how it's drawn. And whatever does the drawing needs to know nothing about a Shape.

    This gives you the flexibility to completely change how the rendering is done, without affecting (or having to recompile!) Shapes, and it also allows you to render something wildly different from a Shape, without having to change the drawing code.