This post is part of a series of reviews on the book Head First: Object Oriented Analysis and Design. Check out the Introduction post for a full table of contents along with some initial notes.
Design principles are nothing more than ways that people found to solve frequent problems, they’re basically techniques that you can apply when writing or designing code to make that code more maintanable, flexible, or extensible. In this chapter we’ll be looking through a few, but before that let’s look at some OO principles we already gathered throughout the last chapters:
- Encapsulate what varies.
- Code to an interface rather than to an implementation.
- Each class in your application should have only one reason to change.
- Classes are about behavior and functionality.
What we have is cool, but there are a few more key principles to look at, let’s start with the Open-Closed Principle (OCP), which states that classes should be open for extension but closed for modification. This means that you should code a class that won’t need to be modified in the future, but should be reajustable to some programmer’s specific needs in a way that he could override some method in a sub-class case he wanted to.
We used this principle in our first design decision back in Chapter 5 for the Instrument Shop, where we made InstrumentSpec a base ‘abstract’ class that could be extended into specific spec classes such as GuitarSpec and InstrumentSpec, so we closed the InstrumentSpec class for modification since the matches() method wouldn’t ever change, but we left it open for extension because all of the subclasses can change the behavior of matches(). Frameworks make huge use of this principle also, imagine if you had to change the framework core code everytime you needed some specific behavior for you application?
As you can see, this principle is all about flexibility, the goal here is to keep your software always open for change.
Moving on to another principle, let’s take a look at DRY, which stands for Don’t Repeat Yourself. As de acronym points out, DRY is all about keeping your code as dry as possible by abstracting out things that are common and placing those things in a single location, therefore avoiding duplicate code. We made use of DRY way back on Chapter 3 when we abstracted the behavior of automatically closing the door to the door itself, instead of repeating it on the BarkRecognizer class when it was already on the Remote class.
Applying DRY is not hard, when you turn a chunk of code into a method so you won’t have to write that same code again, you’re already applying DRY, even by assigning something that’s used lots of times to a variable is already applying DRY. You do it because you wan’t to avoid maintenance problems later on, but also because you want to be sure that a specific behavior will be found at a certain, centered place, instead of scattered all around flooding your code.
To finish it off in a single line: DRY is about having each piece of information and behavior in your system in a single, sensible place.
The third principle we’ll be looking at is called the Single Responsibility Principle (SRP) and states that every object in your system should have a single responsability, and all the object’s services should be focused on carrying out that single responsability, and all the object’s services should be focused on carrying out that single responsability. Turns out we already went through this principle before! So, as a way to practice, let’s use an example class called Automobile that has the following methods: start(), stop(), changeTires(Tire*), drive(), wash(), checkOil(), getOil():int. To know if this class has only one reason to change, we’ll do something called an SRP analysis where you write the following sentence for each method inside the class: “The class_name method_name itself”. Applying this to our example we get:
- The Automobile start[s] itself.
- The Automobile stop[s] itself.
- The Automobile changesTires itself.
- The Automobile drive[s] itself.
- The Automobile wash[es] itself.
- The Automobile check[s]Oil itself.
- The Automobile get[s]Oil itself.
A car can’t possibly change its own tires, nor drive itself, nor wash itself, nor check its own oil, so this is a sign that we should move those methods to specific classes such as Driver, CarWash and Mechanic.
Back to our applications, we applied SRP in a couple of places:
- In the same place we applied DRY on our Dog Door application by moving the automatic door closing feature to the DogDoor class, we also applied SRP to make sure that it handled all its tasks instead of spreading it to other places. It had a sigle responsability.
- In the Instrument Shop we created a matches() method in each instrument so it would compare its specific properties to the ones provided, we didn’t leave it all on the search() method in the Inventory class, because it’s not the Inventory’s duty to compare information with each specific instrument information, that’s the Instrument’s duty.
- By using a hash on our unit properties we avoided that things such a game-specific unit having to go back to the Unit base class to deal with a different set of properties, so we put them all together in the properties hash in the Unit class.
Lastly we’ll be seeing the Liskov Substitution Principle (LSP), which simply tells us that subtypes must be substitutable for their base types. In the post I made about metaprogramming I mentioned the misuse of inheritance with the single purpose of avoiding duplicate code, and I used an example of a Book class having a Hardware class as its parent just so it could get the price accessor method for free. But if we apply the LSP here we’d get all messed up, because there’s no way you can substitute a Book for a Hardware, they’re 2 completely different things. So we know for sure that we’re misusing inheritance here, luckily the post mentioned offers a way around that so you could extend the price behavior by the use of modules.
The HFOOAD book uses a 3DBoard extending the Board class in our Game System Framework from our last 2 chapters, which would also make no sense because a 3D board would use 3 coordinates and thus need a completely different implementation of all the Board methods, so subclassing Board would be a waste if we wanted to create a 3DBoard class. What we could use instead is an array of Board instances which would be pointed by the third dimensional coordinate inside the 3DBoard instance, so we would be faking the 3D behavior without having to rewrite the Board class and still make use of all its methods. When you need to use a functionality in another class but you don’t want to change that functionality, we’re using delegation instead of inheritance.
But say we wanted to use behavior of an interface instead of a single class, such as a Unit class using a Weapon interface that contains Sword, Gun and Club. We wouldn’t want a Unit instance to be tied to a specific weapon such as Sword because it could easily change its weapon to a Club, so we tie it to Weapon instead. The association between a class that uses behavior that can change at runtime from a family of other classes is called composition. The downside about composition is that, using our example, if the Unit instance dies, so will the Weapon instance because they’re so tightened together. In cases that you do need the benefits of composition but also need the composed object to exist outside the main object you can use aggregation, which is being used in the second and most powerful design decision in our Instrument Shop, where Instrument aggregates InstrumentSpec but InstrumentSpec may freely exist without the Instrument, such as the case when a customer supplies one.
The lesson learned here is that by favoring delegation, composition and aggregation over inheritance, your software will usually be more flexible, and easier to maintain, extend, and reuse. But we’ve talked, talked, and talked a bit more, but we left our Game System Framework pending and our customer isn’t all that happy with our delay, so let’s get back to that in the next chapter!