1

I apologize for the potentially misleading and ambiguous title; I've tried to generalize it as best as I can.


I am currently working on an OpenGL project in C++. I wanted to wrap a couple of OpenGL functions into higher level objects. I ran into (what I thought was a unique) problem. I couldn't decide between having a Renderer object be in charge of drawing a Drawable object, or having a Drawable object be in charge of drawing itself with Renderer as a target.

renderer.render(Drawable) vs Drawable.draw(Renderer)

The more I thought about this problem the more I found scenarios where this problem arises.

Some more examples:

writer.write(File) vs file.write(Writer)
video.play(Screen) vs screen.play(Video)
object.toString()  vs toString(Object)

Obviously, for the last one, Java uses object.toString() It got me thinking though. Not every object overrides the toString method, so doesn't that violate the interface segregation principle?

I realized this is a ubiquitous problem. I tried doing some research, but couldn't find the right words to actually search for this problem. That is why I am asking this here.

Is the solution purely subjective, based on design preference? Is the solution different depending on the context like the file and video problems? Please help me understand this more.

10

Billy hits ball

Ball hits Billy

Einstein might tell you it's all relative but the images created in your head by these are vastly different.

Some programmers focus only on mechanics and structure. Viewed with those eyes, yes there is no difference here.

Better programmers make mechanics and structure slaves to semantics. They put in the effort to express themselves fluently. They make their intent something you understand at a glance.

If thinking that way still doesn't make it clear then maybe the method belongs to neither object.

  • ^ this is probably what you end up with when you first try to use your code and realize how fast you will get stuck with OOP when applying to engines. – wondra Apr 9 '18 at 5:22
  • 2
    Going a bit rogue here, I'd say that if you can't think of at least two reasonable places for a method you aren't thinking yard enough. Maybe the method name changes, and maybe one seems more "obvious", but it's usually a complex decision. – user949300 Apr 9 '18 at 6:14
5

If the message asks to change an object, that that one should be the receiver. The only way to change an object should be by sending it a message.

view.draw(drawable);

If one object is in charge of the requested action, that one should be the receiver.

If it's likely to be one of a cascade of messages to an object, that's another good reason to make it the receiver. [I'm using the Dart syntax here. Smalltalk also has syntax for message cascades. Other languages cope by writing reach chained message to return the receiver.]

renderer..draw(circle2)
    ..draw(rectangle1)
    ..setColor(blue)
    ..draw(rectangle2);

iostream..print(summary)
    ..setPrecision(2)
    ..print(total);

notificationBuilder..style(styleObject)
    ..sound(alertTone)
    ..priority(Priority.high)
    ..icon(appIcon)
    ..title(localizedTitle)
    ..body(richTextBody);

When the design decision is not obvious, don't sweat it. Pick one and move on. Revisit it later if it seems awkward. Sometimes there is no best answer, and sometimes one object will just implement the message by delegating to the other anyway.

But I recommend making the choice for O-O design reasons, not by reasoning from class taxonomy, linguistics, or physics.

1

Thinking in terms of message passing will often give you an answer here. Not necessarily the best answer, but probably a good answer, one that makes sense.

The idea here is that the object before the dot is the recipient of a message, and the part after the dot is the message which is being sent.

renderer.render(drawable) vs drawable.draw(renderer)

With the first option, you are telling a Renderer object, "Draw the given Drawable object." With the second, you are telling a Drawable object, "Have yourself drawn by the given Renderer object". The verb "draw" is more straightforward than the verb "have yourself drawn by", so the first option makes more sense.

writer.write(file) vs file.write(writer)

Pretty much the same as above. With the first option, you are telling a Writer object, "Write to the given File object." With the second, you are telling a File object, "Have yourself written to by the given Writer object". The verb "write" is more straight forward than the verb "have yourself written by", so the first option makes more sense.

video.play(screen) vs screen.play(video)

I think this seems similar to the above, but now it feels a little more debatable. With the first option, you are telling a Video object, "Play yourself on the given Screen object." With the second, you are telling a Screen object, "Play the given Video object." I like the second one better.

...although, a screen (the physical device) is merely a passive destination for video data, so if the "Screen" object simply represents a screen, then it shouldn't know how to display a video. If a "Screen" object is some type of screen manager object, then it would make sense for it to know how to display a video.

  • 2
    I believe you have the meaning of writer.write(File) wrong. The writer isn't writing a File, it is writing to a file. – user949300 Apr 9 '18 at 6:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.