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.
- (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.
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.