Monday, August 2, 2010

Am I getting something wrong? (thinking about OOP)

Maybe I am totally wrong but here is what is in my mind. Most of the books that describe object oriented programming, architecture or whatever are making examples using “objects” like person, cat, mammal and then big hierarchy of classes. Of course, more advance books are explaining why huge hierarchy of classes can be wrong and why it is good to use composition. I will not even try to explain that in this post.

And then having these classes you can read about different aspects of OOP. Some of the examples can be:
dog.bark()
person.walk()
person.openDoor()
engine.start()

And I agree that these are almost great examples (except person.openDoor() where I will come back later on).

So let me try to explain where are I am aiming with me question (in the caption of the post).

For learning OOP these may be good examples but from my experience so far these are cases that I don’t meet so often in practice.

Let try one example. Imagine class Can (not verb but noun). Coming from the examples above one could model method: Can.open(). Uhmmm, is it really possible that Can is able to open itself? I wouldn’t say this is reality.

So what is solution here? Quite straight forward. Model a class CanOpener. Then model method CanOpener.open(Can can). This is now much closer to reality.

But what is Can and what is CanOpener from the programming point of view? Well I would say:
Can - is data
CanOpener - is service

And in most of the projects this is what I meet. Most often you are working with data and then perform something on that data. If you try to model your solution as explained at the beginning of the post you will finish with domain classes that have hundred of methods and probably thousands of lines. But very often domain classes are just data while services are actually performing real work. But be careful not to fall into trap of procedural programming.

So come back to example from the beginning: person.openDoor(). As I mentioned this is not good example. And again I would model this quite differently:
Door
DoorOpener (interface)
Person (implements DoorOpener)

It is clear that doors can be opened by person, but they can be opened by engine or wind. So “item” that is able to open door is again service while door is just class keeping data (entity). And yes, you need to add code to open door into Person but again this can be great situation to use composition.

I hope I successfully explained how I try to model solutions for programming problems.

And just short note at the end. Please do not add Service, Manager or similar words at the end of class name. It can easy trap you into procedural programming as you will place too much responsibility into those “managers”.

5 comments:

Unknown said...

Hi Jan,

I see where you are heading, but I cannot agree with all in way you described it. For example I don't think object model should mirror real world model. They are too far away from each other to be useful.

I am afraid some of your suggestions might lead people to procedural code despite the fact you warn against it simply because you suggest separating Can and CanOpener. That's the place where most of programmers are coming from (in my opinion).

I can perfectly imagine an application where a Can can :-) open itself. It all depends on other responsibilities it has and what we want to achieve.

My approach is usually as follows:
- Put responsibilities into classes representing the concepts I am modelling.
- If the class happens to be too big and it has more than one responsibility or it is not focused, I will split it.
- While doing so I try to find abstractions and define interfaces for them (if useful).

This way helps me to find "real" responsibilities contrary to preconceived biases. TDD helps with this. So while I want to start with an idea to put "all" responsibilities inside class for a noun I usually end up with several classes in the end and it is possible I would end up with CanOpener and Can, but I would not start with this split in the beginning (again it always depends on what problem I am solving).

If you start with separation like Can and CanOpener I would bet most of people will end up with Anemic Domain Model - dumb structure and procedural code handling it. Feature Envy is usually good sign of incorrect split of responsibilities. Plus a lot of duplication.

There is another tool I am using for finding better way to split responsibilities. Small methods. That means there will be a lot of methods in class in the beginning, all of them will be focused on something, but not necessarily coherent with rest of class. That's another hint they belong somewhere else.

Pavol

Unknown said...

I forgot to discuss Door & Person :-)

Again, everything depends on why Door exists. Do we want really open some door? Maybe Door should be a state machine (open/closed) and it's method open() should delegate to DoorDriver commanding hardware. DoorDriver would be an interface to allow opening different HW doors.

But if a Door is just representation of volume in some rendering engine, than maybe descriptions of geometry, textures and position would be sufficient in this structure.

It's time to do something with door. Instead of DoorOpener being implemented by Person it seems to me as more useful for Door to implement Openable so that Person can open anything through polymorphism. Again you can argue you are solving another problem and your solution is fine.

BTW Robert Martin claims (and I agree) interface belongs to clients, not to server. Thus interface Openable would not be owned by Door, but by Person (changes in Door would not dictate change in Openable, but changes in way how Person opens things could lead to the change of interface).

Your example of DoorOpener is valid too, let's say in simulation of different personalities (one will knock, other will kick it), but I think your post concentrated on responsibilities for concept of door.

Pavol

Unknown said...

Just a quick note:
can.open does not mean the can opens itself, but another related object can open the can (the can allows it to be opened)

Alexander Ashitkin said...

i agree with the author. oo style isn't sufficient if you need to operate on data objects. so the right way to choose style is to understand your requirements. I found this idea in the "Clean code" book:
Objects expose behavior and hide data. This makes it easy to add new kinds of objects
without changing existing behaviors. It also makes it hard to add new behaviors to existing
objects. Data structures expose data and have no significant behavior. This makes it easy to
add new behaviors to existing data structures but makes it hard to add new data structures
to existing functions.
In any given system we will sometimes want the flexibility to add new data types, and
so we prefer objects for that part of the system. Other times we will want the flexibility to
add new behaviors, and so in that part of the system we prefer data types and procedures.
Good software developers understand these issues without prejudice and choose the
approach that is best for the job at hand.

Unknown said...

Raphael's comment seems spot on.
Public functions provide the interface for an object.

When you said:
"Instead of DoorOpener being implemented by Person it seems to me as more useful for Door to implement Openable so that Person can open anything through polymorphism."

... you are actually saying to use the original can.open(). Only when the problem arises, or you can foresee, that multiple things need to be opened, it will be useful to create a Openable interface. Otherwise you would be creating a separate interface for every function.

For me this seems to be the best implementation.

I've been reading through the book "Clean Code" and I find it contains some bad statements, one of them being the one Alexander quoted.

John discusses this point here:
http://jd-syntropy.blogspot.com/2009/01/dataobject-anti-symmetry.html

I haven't reached the part about where interfaces belong yet, so I'm hoping for it to be an interesting read.