- Encapsulate what varies.
- Favor composition over inheritance.
- Program to interfaces, not implementations.
- Strive for loosely couple designs between objects that interact.
- Classes should be open for extension but closed for modification.
- Depend on abstractions. Do not depend on concrete classes.
- Only talk to your friends.
- Don’t call us, we’ll call you.
- A class should only have one reason to change.
Continuing from the basics of OO, we now move on to its principles. The list above is taken from the wonderful book Head First Design Patterns and it enumerates some of the most important principles in OO. I’ll explain briefly what each principle means below the cut.
—
Encapsulate what varies. – can be rephrased as “Identify the aspects of your application that vary and separate them from what stays the same.” For those aspects that don’t change, we leave them as they are. Of course, we can’t just throw away those parts that vary; the former still needs to interact with them in order for the system to work.
Here’s where encapsulation steps in: in order to manage the complexity of our system, we hide the inner details of those aspects that vary using encapsulation. The aspects that don’t change can now interact with them–they just don’t care about how they work.
This principle might be confusing at first, but hopefully the other principles will further explain how this principle works.
Favor composition over inheritance. – Inheritance may be a fundamental feature of OO, but it is also a very dangerous one. For most novice programmers, inheritance seems like the only way to implement code reuse and extend existing functions in systems.
The problem with inheritance is that it is not as flexible as it looks. This is apparent in “fragile base classes” wherein the effects of poor design in base classes cascade down to descendant classes with no way to easily correct them.
Composition gets around these limitations. In practical cases, by plugging in functionality to an existing class we can get the same results as inheritance. Now if we find this new functionality faulty, we can just replace it in real-time, as opposed to having to recompile the program with inheritance.
Inheritance does have its uses. Most of the time, however, it’s better to use composition or (as the later principles will mention) interfaces and abstractions.
Program to interfaces, not implementations. – by “interface”, this principle isn’t limiting itself to Java interface
s, it also covers supertypes, preferably abstract classes or interfaces, both of which refer to classes with undefined behaviors.
There are some reasons why you should prefer interfaces over implementations (classes with defined behaviors) and some of them will be covered as we go along with these principles. The simplest reason why you should do so is to allow polymorphism to take place. By dealing with objects through interfaces, we can get different behavior even without knowing what the actual object is.
Strive for loosely couple designs between objects that interact. – coupling, as mentioned in the previous post, is the level of interdependence between objects. The more one object knows and cares about the implementation of another object, the tighter the coupling between them.
Without proper application of OO principles, you get objects that interact with each other in complicated ways. One object might directly modify a variable of another object, and another object might access the functions of another object in some unorthodox way just so the system would work. The problem with this scenario is that if you change the implementation details of one object, you are almost certain that you would break other objects too. In other words, tight coupling.
By hiding the implementation details of objects that change (first principle above) through interfaces (previous principle), you now limit the ways wherein objects can interact with each other. When you now change the implementation details of one object, not only will the chance of breaking other objects be lower, limiting the amount of ways other objects can interact with the object will also limit the effects of a faulty change.
Ironically, by limiting the interactions between your objects, you can make your system much more flexible.
Classes should be open for extension but closed for modification. – also known as the Open-Closed Principle. While it looks self-contradictory, it merely states that you should design classes such that they can be extended without changing their code. For example, composition over inheritance.
Note that it is a bad idea to open all of your modules for extension in your system because it introduces complexity. Therefore, this principle must only be used in moderation.
Depend on abstractions. Do not depend on concrete classes. – similar to interfaces, not implementations but takes it to another level. Also goes by the name Dependency Inversion Principle.
For example, if you’re dealing with food objects in your system, you’d want use a Food
interface to allow general processing instead of more concrete implementations like Burger
or Pizza
which would require specific processing. By doing the former, you reduce the complexity of your system, a feat that becomes apparent when you take into account polymorphism.
Only talk to your friends. – aka The Law of Demeter, or Principle of Least Knowledge. It’s a simple set of guidelines to make your programs easier to manage.
In any method of an object, you should only invoke methods that belong to:
- The object itself
- Objects passed in as a parameter to the method
- Any object the method creates or instantiates
- Any components of the object
For example, the following code violates this principle
public float getTemp() { return station.getThermometer().getTemperature(); }
because when you expand it:
public float getTemp() { Thermometer thermometer = station.getThermometer(); return thermometer.getTemperature(); }
we see that we are dealing with an object (thermometer
) that does not fall under any of the four criteria above. Ideally, we should modify the class of station
so that the following code would work:
public float getTemp() { return station.getTemperature(); }
This is another principle that should be used with moderation. When dealing with heavily nested objects, this principle might be counterproductive as it would add a lot of functions to your existing classes.
Don’t call us, we’ll call you. – aka the Hollywood Principle.
In this principle, low-level components do not call high-level components, instead it’s the other way around. Think of it as a combination of open-closed principle and dependency inversion: the low-level components that extend the high-level components are invoked by the latter instead of the other way around.
This way, we don’t have problems when the low-level components under the same high-level component need to interact with each other because the latter is the one who actually initiates the interactions. If it were up to the low-level components, we’d end up with a tangled mess of “sideways” interactions that is hard to understand, let alone maintain.
A class should only have one reason to change. – this principle deals with cohesion, how classes should, as much as possible, have only one focused responsibility.
If a class has multiple responsibilities, it has multiple reasons to change. More reasons to change means a higher possibility of change, and a higher possibility of change means more work, if not “more chance of adding bugs”.
It’s a simple principle, but it’s very hard to do in practice. Most of the classes you’ll create will deal with more than one responsibility. At the very least, you should strive to extract the unrelated responsibilities from your classes and make them more focused: no one needs a single class that calculates your payroll, washes your dishes, and drives you to your office.
—
So there you have it, some of the most important OO principles around. Bet you didn’t know OOP could be this complicated. :P
Next up would probably be refactoring, a fun little topic that deals with making your code better without changing how it works.
[…] Object Oriented Principles […]