10

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?

Mik378
  • 21,881
  • 15
  • 82
  • 180
Tek
  • 2,888
  • 5
  • 45
  • 73

7 Answers7

4

OOP promotes sending messages, on the contrary of procedural code that "asks" for some external data and then process.

If you place the draw method in the renderer, you would break the encapsulation of the class Shape, since it certainly would need to access its internals (like coordinates (x,y) etc..).

By letting Shape draw "itself", you keep encapsulation promoting flexibility regarding internal changes.

The solution really depends of the complexity.
By extracting the draw method from Shape, your shape would need to expose its data.
By keeping it, you keep encapsulation.

Thus, if your draw is complex, prefer considering it as another whole responsibility carried by a Renderer or Graphics, thus corresponding to your suggestion.

Mik378
  • 21,881
  • 15
  • 82
  • 180
  • What exactly is meant by the Single Responsibility Principle in that case then? How can we do many different tasks with `Shape` without breaking encapsulation and keeping SRP behaviour? – Tek Aug 08 '13 at 20:47
  • @Tek SRP doesn't strictly mean "one task", but "one responsibility". A `shape`could do many different things, as long as, those behaviors really fit the global responsibility of the object, that is: "describing all tasks that Shape can do" – Mik378 Aug 08 '13 at 20:57
  • So lets say I wanted to log a history of how many times `Shape` has changed into different dimensions. I don't think it's the responsibility of Shape to log and keep track of those changes... would injecting a logger and calling `Shape->log(logger)` be the right thing to do? – Tek Aug 08 '13 at 21:07
  • Your model class should avoid infrastructure/framework tools like logger. It should be ignorant of any external dependencies. In your case, I would implement a `Listener` that listens for the the shape events, processing the logs so. Very similar to this: http://docs.oracle.com/javase/tutorial/uiswing/events/actionlistener.html – Mik378 Aug 08 '13 at 21:17
  • I understand what you mean I just don't see how it'd be wired. I'm confused by the fact that we're supposed to pass `Shape` information to the logger... without `Shape` knowing anything about logger... – Tek Aug 08 '13 at 21:34
  • @Tek Polymorphism is the answer :) Always prefer to program by interface, not concrete implementation (but sometimes we can/should). `Shape` would not know directly about `Logger`, it should contain a field like for instance: `private Set listeners;` and everywhere you need to notify listeners (logger in your case), you would do in `Shape` class methods: `listeners.perform(myEventOrDataIWantToNotify)`. And in the other side, you would have: `Logger implements Listener` containing a method: `onEvent(YourEventOrData eventOrData)` that logs the event – Mik378 Aug 08 '13 at 21:38
  • Look at this pattern, it represents exactly the concept I explained just above: http://www.oodesign.com/observer-pattern.html – Mik378 Aug 08 '13 at 21:46
  • Isn't that what I originally said? We're passing the NewsPublisher class to the subscriber class which then retrieves the information via the getter `getLatestNews()`. Which in this example Shape could be passed to a logger then retrieve the value in the same way via a getter... Is there something I'm missing? – Tek Aug 08 '13 at 21:57
  • your example is not similar, since it would access internals of Shape, that breaks encapsulation. Logger should not depend on shape class, but on an event type gathering some useful information to be logged – Mik378 Aug 08 '13 at 22:08
  • @Tek Moreover, with your suggestion, you would deal with manual notification (by calling your `getLatestNews`). However, with an observer pattern, you don't "ask", you "observe" (listen) :), much better – Mik378 Aug 08 '13 at 22:33
  • Okay, I'm starting to get it little by little. Bear with me :) Now you said it should be ignorant of any dependencies... so how is `Shape` supposed to draw/render without providing it a `Renderer`? – Tek Aug 09 '13 at 03:08
  • @Tek Actually, for real draw, not a simple sample code like yours and this one: http://www.angelfire.com/tx4/cus/shapes/java.html, you would need to extract it to a Graphics for example. Look at awt: Here a Shape: Rectangle docs.oracle.com/javase/7/docs/api/java/awt/Rectangle.html without draw method in it. Here the renderer: docs.oracle.com/javase/7/docs/api/java/awt/Graphics2D.html – Mik378 Aug 09 '13 at 09:47
  • Isn't the AWT example what I had described? It seems the `draw()` is using the `Shape` as an argument to render it... Which is what I had described earlier. – Tek Aug 09 '13 at 14:26
  • @Tek Yes, indeed. IMO, the solution really depends of the complexity. By extracting the `draw` method from `Shape`, your shape would need to expose its data. By keeping it, you keep encapsulation. Thus, if your `draw`is complex, prefer considering it as another whole responsibility carried by a `Renderer` or `Graphics`, thus corresponding to your suggestion. – Mik378 Aug 09 '13 at 14:30
  • 1
    I'd accept this answer if you added your last comment makes your answer seem more clear. – Tek Feb 09 '15 at 18:57
  • I was looking through my questions and it seems I didn't accept your answer. Probably for the reason above. Thanks. :) – Tek Feb 09 '15 at 19:59
4

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.

Dwayne Towell
  • 8,154
  • 4
  • 36
  • 49
  • Well, I guess this is no surprise given my answer, but I can't see any situation where `Document` should have a print function. A document is something that just is. It doesn't do anything. A printer is a machine that accepts a digital document and outputs it in a different form (paper, PDF). Why should this analogy be changed? A `Printer` interface can abstract away all differences (dot matrix, laser, OS version). – aliteralmind Jul 04 '14 at 15:06
  • You are making the assumption that all printers are essentially the same. Having worked on a real project that considered all the following to be "printers", I can attest that having the printer decide how to render a document is NOT always the right answer. Example printers: plotter, postscript, daisy wheel printer, remote slide rendering service, and then there are all the ones you are referring to--all points addressable. In this situation, it can make more sense for the printer to decide how to render the document, than for the document to try. – Dwayne Towell Jul 04 '14 at 18:40
4

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.

batwad
  • 3,588
  • 1
  • 24
  • 38
3

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.

Community
  • 1
  • 1
aliteralmind
  • 19,847
  • 17
  • 77
  • 108
1

Only an object truly knows how to draw itself.

Imagine a locksmith... hes can pick 1000 different lock types. I can go to the store, buy any lock and give it too him and he can pick it, because he is familiar with lock-tech.

Now imagine I am an inventor and I start making my own locks, unique in design, revolutionary. Is he going to be able to open them? Perhaps, but perhaps not... It depends what I did in side in the lock,.. am I using tech he/she is aware of, etc?

Your shape objects are the same,... depending how they are implemented inside determines whether they can be rendered by some generic render engine or not. If you ask each object to draw itself then you do not have to worry about this.

Chris
  • 2,655
  • 2
  • 18
  • 22
1

The above answers seem entirely over-complicated to me.

The purpose of the shape and circle example is to delineate the difference between interface (how it's expected to talk to the outside world) and implementation (how it's expected to behave.)

The problem with the example you gave is that it's been truncated. It makes more sense when there are more shapes involved.

Consider the case of having a circle, a triangle, and a rectangle. Now how is Shape going to draw itself? It doesn't know which kind it is, or as such, what to do.

Now consider a container of shapes. They all have a draw method; the parent class enforces that. So, you can have a container of shapes that's homogenous, even though the implementations of their various drawing methods are essentially unrelated.

Why does Circle draw itself, rather than shape? Because it knows how to.

John Haugeland
  • 9,230
  • 3
  • 37
  • 40
  • +1 for the container example because it adds some meat to the canonically trivial `Shape` example. – batwad Jul 07 '14 at 17:09
0

Pure virtual functions are used for when the behavior of the algorithm isn't clearly defined for a set, but the existence of the algorithm is clearly defined for a set.

I hope this isn't too much to digest, but perhaps a lesson from functional analysis is relevant. I digress about the set theoretical implications of virtual functions.

Let the family of sets A have the property {x : P(x)}
Let A be an element of the family A
Let A' also be an element of the family A

A and A' could fall under one of the following three catagories.
(1) A and A' are equivalent
For all a elements of A, a is an element of A'
AND for all b elements of ~A, b is an element of ~A'

(2) A and A' intersect
There exists a elements of A where a is an element of A'
There also exists b elements of A where b is an element of ~A'

(3) A and A' are disjoint
There does not exist an element a of A that is also an element of A'

WHERE ~X refers to all x that are not elements of the set X

In case (1) we would define a non-abstract behavior iff U being an element of the family A implies the existence of a single value u such that u = P(U) for all U that are elements of the family A

In case (2) we would define a virtual behavior if U being an element of the family A implies the existence of a single value u such that u = P(U') where U' is a subset of U.

And in case (3) we would define a pure virtual behavior because A and A' are only similar in that they are both members of the family A, such that the intersection of A and A' is the empty set, implying that there does not exist any common elements of A and A'

Think about what the syntax means in terms of the logical definitions and you'll be able to answer the following:

(1) Does the function need to be abstract? (no for case 1, yes for case 2 and 3) (2) Does the function need to be pure virtual? (no for case 1 and 2, yes for case 3)

In case 2, it also depends on where the behavior's necessary information is being held -- in the base class or in the derived class.

You can't render a SHAPE from a DISPLAY, without the DISPLAY looking for information that isn't necessarily part of the definition of SHAPE. Inasmuch as DISPLAY can't see into the definition of a type derived from SHAPE, beyond what is defined for SHAPE. So any functionality that depends on information contained in a derived type must be defined for the abstracted function within the derived class.

JustKevin
  • 378
  • 3
  • 13