Monday, October 12, 2009

Object-oriented myths

I think object-oriented programming techniques should be explained with a context and not shouted out loud as philosophical principles. Sometimes these techniques are misinterpreted, or exaggerated and used as a weapon against oop proponents.
Some popular myths about oop have risen in the programming world, particularly in the php continent where object orientation is still in its infancy. I want to debunk these legends to show that good oop is actually easier than you think. For instance, Dependency Injection is one of the best thing that can happen to your code.

"You should not expose public fields. Write getters and setters instead."
The point of encapsulation is to protect client classes from changes in the collaborators. In what is using a set*() method different from a public field, for the cause of encapsulation?
There are some classes which only responsibility is to maintain state. We encounter them in every project, being their names User, Post, CreditCard, String, Regex and so on. It is correct for this type of classes to have getters and setters to change their internal state and fulfill their requirements.
The other type of classes are services: stateless objects which do complex work, are not serializable and may have internal and external dependencies, towards other services or processes. These classes should not have any get*() or set*() method, since they are by definition stateless and the list of getters will break encapsulation. If I were a client class I would require a BookRepository object to search books, because I use it to... search books and not to change the underlying database calling BookRepository::setDb(Zend_Db $db). It's not my job to pass the collaborators in: I use the object only to search books.
If you find a complex service class which also has a state, the most useful thing to do is to break up the class in little ones because the responsibility is too high for a single unit. Testability and cohesion will improve.
So without setters, how I get collaborators references in, for example, a ServiceClass object private fields? You can create them directly in ServiceClass methods (bad) or realize Dependency Injection, passing them in the constructor of ServiceClass. A Factory will then call new, passing in the collaborators and encapsulating the creation. This practice leads us to the next myth.

"Why using a factory? There's no point in creating an object only to create another one."
The process of an object's creation can be intensive and complicated. If you use Dependency Injection (and you probably should as it is a standard practice for producing solid object-oriented code), every object needs its collaborators passed in the constructor, and the collaborators need other collaborators and so on. It's useful to abstract away this process in a Factory class whose methods perform the construction in one centralized place. Again, entity objects like Strings, Users and Posts are a bit special and using a Factory for them is not required, provided that they don't even had collaborators.
Though, if you find yourself creating a factory and subsequently, in the next line of code, using its methods to create a business object, chances are the design can be improved. The principle is that objects should ask for things, and not look for them.
Let's make the following assumptions:
  • You write object-oriented applications, so all your code is kept in classes. This is not necessarily true for hybrid languages like php.
  • You strive for minimizing the coupling of your components, respecting the Law of Demeter. You want to reuse classes and you want to be able to test them in isolation.
So start with examining where you are creating a BusinessObject in a class Client. The possible cases are three (actually two):
  • (not possible) BusinessObject has a longer lifecycle than Client, so you cannot create it in Client. Ask for it in the constructor.
  • BusinessObject has the same lifecycle of Client: it's a collaborator. So stop using a factory and simply ask for BusinessObject in the constructor of Client.
  • BusinessObject has a shorter lifecycle than Client: it must be created at a specific time, when some method on Client is called.
The latter case is the most interesting one. If BusinessObject is an entity, you can create it directly: there are no external dependencies to inject and there is little behavior to mock out in tests. If BusinessObject is a service class, with one or more external dependencies and more than a bit of behavior, you should indeed use a Factory to encapsulate its creation. But since you strive to reduce coupling, Client must not know if BusinessObject calls methods on an hundred different objects or returns prepackaged results. So you should pass in a Factory object in the constructor of Client: this solution resembles an Abstract Factory pattern, with the difference that we are focusing on lifetimes and not on extensibility and introduction of new subtypes.
In sum, all direct service object creation (use of the new operator) should be encapsulated in Factories, which can be used in the application bootstrap or passed in the constructor of the Client class if you need object creation as a business requirement (and the object is more complex than a String). Obviously if you are using setter injection it does not make a great difference, but the Api will be more cluttered. If your setters does not accept multiple calls to change collaborators, they have my blessings.
Warning: all the class names used are examples. For instance Client and BusinessObject are two classes that could also be called A and B. I often use Client to denote a class which is attempting to create an object, according to the GoF terminology. BusinessObject is a generic name for a class, more readable than Foo and Bar.

This post is becoming too long to not lose focus, so I'll continue tomorrow with the rest of object-oriented myths.
You may want to subscribe to the feed to discover the other myths as soon as they are published.


Jafar said...

Interesting article. I fear I may have missed some things because if your terminology. Word like business objects are a little confusing. I look forward to your next article.


Giorgio said...

I've add a paragraph explaining that these names are mostly examples (like BookRepository is). Client is only the standard name for the class which utilizes a pattern, such as an Abstract Factory. I use real names to distinguish between them in a phrase.

zampano said...

Hi Giorgio,

when you talk of "entities", is it the same "pattern" like that one in DDD?

Giorgio said...

I use "entity" as a wider term for describing every object which purpose in the domain model is to maintain state. This includes DDD Entity and Value Objects, but also every "newable" object like Java String and Map.
For a detailed explanation see:

zampano said...

Then I don't understand this sentence:
"If BusinessObject is an entity, you can create it directly"

Staying with the DDD pattern Entity, then it not only maintains its state but also its invariants at creation time.
And it also could be the root of an Aggregate.
The creation could be very complex, so I would almost never
do it without a Factory (or another creational pattern) for that ones.

Another reason is also the creation of complex entities as a Whole object (not allowing partially invalid objects).

Most of my Entities aren't simply "newable".

And another question is: "Who creates an Entity"?
Does it come out of nowhere, can I "new" it anywhere?

Perhaps I'm only a bit confused by your use of the terms entity/service.
Extended use of Services, at least in DDD, should be avoided where possible, else it could lead to an anaemic domain model soon.

Just a food for thought.

Thanks for great articles!

Giorgio said...

You're right, the different uses of "Entity" term overlap here. The distinction Entity/Service can also be called newable vs. injectable and that's what I mean. If a DDD-Entity has reference to other objects, it becomes an injectable and so a factory is the right choice. The direct creation is limited to objects without collaborators (so there's no injection to abstract away) and with little behavior (so there are no problems in testing them in isolation). Examples are String, DateTime, ArrayObject... and also User if it's not a rich entity. In DDD the problem is finding a balance between rich/coupled and cohesive/anemic, and I prefer to never inject a service in an entity but only to pass it as a parameter when needed.