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?
2 verb If you inherit something such as a task, problem, or attitude, you get it from the people who used to have it, for example because you have taken over their job or been influenced by them.
-ˈhe-rə- : something that is or may be inherited. : the act of inheriting property. : the reception of genetic qualities by transmission from parent to offspring. : the acquisition of a possession, condition, or trait from past generations.
Inheritance is the practice of passing on property, titles, debts, and obligations upon the death of an individual. It has long played an extremely important role in human societies, and a variety of Inheritance laws have been developed to regulate the process.
It is a process which involves the passing on of material property from one generation to another, usually within the family, generally from older parents (donors) to their adult children (heirs), which is completed after the death of the older generation.
I often find that these simple textbook examples fail to explain the reason sufficiently because they are overly simplistic. There are lots of things we could give a Shape
class the responsibility of doing: drawing itself, computing its area, working out whether a given point lies within its bounds, working out what shape result from the intersection of another shape, remembering how many people count it as their favourite shape... the list is only as long as your imagination and which responsibilities you give it depends on the goals of your program and how you choose to build it.
Assuming you want to be able to draw the shapes in a generic, polymorphic way consider how you might actually implement that. Onto what exactly will the shape be drawn? Will the shape know the canvas works? Should it know that it needs to pick up a paintbrush, dip it in some paint and then draw itself? Should it know how your display driver works? Set bits to turn on pixels in the right place so that your monitor shows the right shape?
Clearly going down to this level is giving the shape far too much responsibility, so instead you define a set of graphical primitives (e.g.: points and lines) and build a graphics API that can render these. A Shape
can then use the primitives to tell the API what to draw. The graphics API doesn't know it's drawing a square, but by telling it to draw four lines hey presto it's draw a square. All this leaves a Shape
with the single responsibility of knowing its points and the lines that define it.
It's always difficult to see the benefits of certain design patterns when you take classes in isolation and that is because building software is about getting things to work together; nothing ever works in isolation.
The choice to make Draw()
a method of the base class depends on the context--the specific problem being solved. To make the problem slightly more clear here is another example which I regularly used while interviewing for OO skills.
Imagine a Document class and Printer class. Where should the print function go? There are two obvious choices:
document.print(Printer &p);
or
printer.print(Document &d);
Which is the Right one? The answer is: it depends on where you want polymorphic behavior--in the document or in the printer. If we assume that all printers have identical functionality (a myth operating systems attempt to promote), the clearly the polymorphic behavior should be in the Document object. However, if we imagine that all documents are approximately the same (or at least the ones we care about) and that printers are vastly different (this used to be the case--consider: plotters, line printers, laser printers, daisy wheel printers, etc.) then it makes more sense to let the printer decide how best to render the document.
One could argue that Print()
should be part of neither object since polymorphic behavior may be desire from a combination of both printer and document. In this case you need double dispatch.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With