Chapter 8. Design Principles: Originality is Overrated

image with no caption

Imitation is the sincerest form of not being stupid.

There’s nothing as satisfying as coming up with a completely new and original solution to a problem that’s been troubling you for days—until you find out someone else solved the same problem, long before you did, and did an even better job than you did! In this chapter, we’re going to look at some design principles that people have come up with over the years, and how they can make you a better programmer. Lay aside your thoughts of “doing it your way”; this chapter is about doing it the smarter, faster way.

Design principle roundup

So far, we’ve really been concentrating on all the things that you do before you start coding your application. Gathering requirements, analysis, writing out feature lists, and drawing use case diagrams. Of course, at some point you actually are going to have to write some code. And that’s where design principles really come into play.

Note

image with no caption

A design principle is a basic tool or technique that can be applied to designing or writing code to make that code more maintainable, flexible, or extensible.

You’ve already seen a few design principles in earlier chapters:

Using proven OO design principles results in more maintainable, flexible, and extensible software.

OO Principles

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.

In this chapter, we’re going to look at several more key design principles, and how each one can improve the design and implementation of your code. We’ll even see that sometimes you’ll have to choose between two design principles... but we’re getting ahead of ourselves. Let’s begin by looking at the first of our design principles.

Principle #1: The Open-Closed Principle (OCP)

Our first design principle is the OCP, or the Open-Closed principle. The OCP is all about allowing change, but doing it without requiring you to modify existing code. Here’s how we usually define the OCP:

image with no caption

Note

image with no caption

Open-Closed Principle

Classes should be open for extension, and closed for modification.

Closed for modication...

Suppose you have a class with a particular behavior, and you’ve got that behavior coded up just the way you want it. Make sure that nobody can change your class’s code, and you’ve made that particular piece of behavior closed for modification. In other words, nobody can change the behavior, because you’ve locked it up in a class that you’re sure won’t change.

image with no caption

... but open for extension

But then suppose someone else comes along, and they just have to change that behavior. You really don’t want them messing with your perfect code, which works well in almost every situation... but you also want to make it possible for them to use your code, and extend it. So you let them subclass your class, and then they can override your method to work like they want it to. So even though they didn’t mess with your working code, you still left your class open for extension.

image with no caption

Remember working on Rick’s Stringed Instruments?

You probably didn’t realize it, but we were using the Open-Closed Principle when we wrote those InstrumentSpec classes for Rick’s Stringed Instruments, back in Chapter 5:

image with no caption

InstrumentSpec is closed for modification; the matches() method is defined in the base class and doesn’t change.

But it’s open for extension, because all of the subclasses can change the behavior of matches().

The OCP, step-by-step

Let’s take what we did back in Chapter 5, and look at in terms of the OCP, one step at a time.

image with no caption
  1. We coded matches() in InstrumentSpec.java, and closed it for modification.

    This version of matches() works just fine, and we don’t want anyone messing with it. In other words, once we’re done coding InstrumentSpec and this version of matches(), they shouldn’t change.

    image with no caption
  2. But we needed to modify matches() to work with instrument-specific spec classes.

    Even though matches() works great for other InstrumentSpec objects, it doesn’t quite do what it should for guitars and mandolins. So even though matches() is closed for modification, we need a way to extend and change it... otherwise, InstrumentSpec isn’t very flexible, which is a big problem.

  3. So we extended InstrumentSpec, and overrode matches() to change its behavior.

    We don’t want to change the code in InstrumentSpec, but we can extend it, with GuitarSpec and MandolinSpec, and then override matches() in each of those classes to add instrument-specific behavior.

    image with no caption
image with no caption
image with no caption

The OCP is about flexibility, and goes beyond just inheritance.

It’s certainly true that inheritance is a simple example of the open-closed principle, but there’s a lot more to it than just subclassing and overriding a method. Anytime you write working code, you want to do your best to make sure that code stays working... and that means not letting other people change that code.

But there are going to be times when that code still needs to be changed, maybe for just one or two particular situations. Rather than just diving into your code and making a bunch of changes, the OCP lets you extend your working code, without changing that code.

There are lots of different ways to accomplish this, and while inheritance is often the easiest to implement, it’s certainly not the only option. In fact, we’ll talk about another great way to achieve this later in the chapter, when we talk about composition.

Principle #2: The Don’t Repeat Yourself Principle (DRY)

Next up is the Don’t Repeat Yourself principle, or DRY for short. This is another principle that looks pretty simple, but turns out to be critical in writing code that’s easy to maintain and reuse.

Note

image with no caption

Don’t Repeat Yourself

Avoid duplicate code by abstracting out things that are common and placing those things in a single location.

A prime place to apply DRY...

You’ve seen the DRY principle in action, even if you didn’t realize it. We used DRY back in Chapter 2, when Todd and Gina wanted us to close the dog door automatically after it had been opened.

image with no caption

1. Let’s abstract out the common code

Using DRY, we first need to take the code that’s common between Remote and BarkRecognizer, and put it in a single place. We figured out back in Chapter 2 the best place for it was in the DogDoor class:

image with no caption

2. Now remove the code from other locations...

3. ...and reference the code from Step #1

The next two steps happen at the same time. Remove all the code that you put in a single place in Step #1, and then reference the code you abstracted out explicitly if you need to:

image with no caption

DRY is really about ONE requirement in ONE place

Abstracting out duplicate code is a good start to using DRY, but there’s more to it than just that. When you’re trying to avoid duplicate code, you’re really trying to make sure that you only implement each feature and requirement in your application one single time.

In the dog door we just looked at, the feature we were trying to implement was automatically closing the door.

image with no caption

Originally, though, we implemented that single feature in two places: Remote.java and BarkRecognizer.java.

image with no caption

By using DRY, we removed the duplicate code. But more importantly, we moved the implementation of this requirement, automatically closing the door, into one place, instead of two places:

image with no caption

Principle #3: The Single Responsibility Principle (SRP)

The SRP is all about responsibility, and which objects in your system do what. You want each object that you design to have just one responsibility to focus on—and when something about that responsibility changes, you’ll know exactly where to look to make those changes in your code.

Note

image with no caption

Single Responsibility Principle

Every object in your system should have a single responsibility, and all the object’s services should be focused on carrying out that single responsibility.

image with no caption

You’ve implemented the Single Responsibility Principle correctly when each of your objects has only one reason to change.

Spotting multiple responsibilities

Most of the time, you can spot classes that aren’t using the SRP with a simple test:

  1. On a sheet of paper, write down a bunch of lines like this: The [blank] [blanks] itself. You should have a line like this for every method in the class you’re testing for the SRP.

  2. In the first blank of each line, write down the class name; in the second blank, write down one of the methods in the class. Do this for each method in the class.

  3. Read each line out loud (you may have to add a letter or word to get it to read normally). Does what you just said make any sense? Does your class really have the responsibility that the method indicates it does?

If what you’ve just said doesn’t make sense, then you’re probably violating the SRP with that method. The method might belong on a different class... think about moving it.

image with no caption

Going from multiple responsibilities to a single responsibility

Once you’ve done an analysis, you can take all the methods that don’t make sense on a class, and move those methods to classes that do make sense for that particular responsibility.

image with no caption

Contestant #4: The Liskov Substitution Principle (LSP)

Next up in our design principle parade is the Liskov Substitution Principle, or the LSP. It’s definition is as simple as it gets:

image with no caption
image with no caption

The LSP is all about well-designed inheritance. When you inherit from a base class, you must be able to substitute your subclass for that base class without things going terribly wrong. Otherwise, you’ve used inheritance incorrectly!

Misusing subclassing: a case study in misusing inheritance

Suppose that Gary’s Games has a new client who wants to use their game system framework to create World War II air battles. They need to take the basic Board base type, and extend it to support a 3-dimensional board to represent the sky. Here’s what they’ve done:

image with no caption

Make it Stick

One of these things is just like another, Use the base or its subclass, it’s not a bother, Substitute, exchange, it doesn’t rattle me, They all work the same, they use the LSP!

LSP reveals hidden problems with your inheritance structure

At first glance, it may seem like subclassing Board and using inheritance is a great idea. But look closer, there are lots of problems that this approach creates:

image with no caption

The 3DBoard class is not substitutable for Board, because none of the methods on Board work correctly in a 3D environment. Calling a method like getUnits(2, 5) doesn’t make sense for 3DBoard. So this design violates the LSP.

Note

Even worse, we don’t know what passing a coordinate like (2,5) even means to 3DBoard. This is not a good use of inheritance.

“Subtypes must be substitutable for their base types”

image with no caption

We already said that LSP states that a subtype must be substitutable for its base type. But what does that really mean? Technically, it doesn’t seem to be a problem:

image with no caption

But when you start to actually use that instance of 3DBoard like a Board, things can get confusing very fast:

image with no caption

So even though 3DBoard is a subclass of Board, it’s not substitutable for Board... the methods that 3DBoard inherited don’t have the same meaning as they do on the superclass. Even worse, it’s not clear what meaning those methods do have!

image with no caption

Violating the LSP makes for confusing code

It might seem like this isn’t such a big deal, but code that violates LSP can be confusing, and a real nightmare to debug. Let’s think a bit about someone who comes to use the badly designed 3DBoard for the first time.

They probably start out by checking out the class’s methods:

image with no caption

It’s hard to understand code that misuses inheritance.

When you use inheritance, your subclass gets all the methods from its superclass, even if you don’t want those methods. And if you’ve used inheritance badly, then you’re going to end up with a lot of methods that you don’t want, because they probably don’t make sense on your subclass.

So what can you do to avoid this? First, be sure your subclasses can substitute for their base types, which is just following the LSP. Second, learn about some alternatives to using inheritance in your code...

image with no caption

Solving the 3DBoard problem without using inheritance

image with no caption

It’s not enough to just know that inheritance isn’t the answer... now we’ve got to figure out what we should have done. Let’s look at the Board and 3DBoard classes again, and see how we can create a 3-dimensional board without using inheritance.

image with no caption

So what options are there besides inheritance?

Delegate functionality to another class

You’ve already seen that delegation is when one class hands off the task of doing something to another class. It’s also just one of several alternatives to inheritance.

Note

image with no caption

Delegation is when you hand over the responsibility for a particular task to another class or method.

Delegation was what we used to solve the 3DBoard problem we’ve been looking at, without resorting to inheritance:

image with no caption

When to use delegation

Delegation is best used when you want to use another class’s functionality, as is, without changing that behavior at all. In the case of 3DBoard, we wanted to use the various methods in the Board class:

image with no caption

Since we don’t want to change the existing behavior, but we do want to use it, we can simply create a delegation relationship between 3DBoard and Board. 3DBoard stores multiple instances of Board objects, and delegates handling each individual board-related task.

image with no caption

If you need to use functionality in another class, but you don’t want to change that functionality, consider using delegation instead of inheritance.

Use composition to assemble behaviors from other classes

Sometimes delegation isn’t quite what you need; in delegation, the behavior of the object you’re delegating behavior to never changes. 3DBoard always uses instances of Board, and the behavior of the Board methods always stay the same.

But in some cases, you need to have more than one single behavior to choose from. For example, suppose we wanted to develop a Weapon interface, and then create several implementations of that interface that all behave differently:

image with no caption

Now we need to use the behavior from these classes in our Unit class. One of the properties in our properties Map will be “weapon”, and the value for that property needs to be an implementation of the Weapon class. But a Unit might change weapons, so we don’t want to tie the weapon property to a specific implementation of Weapon; instead, we just want each Unit to be able to reference a Weapon, regardless of which implementation of Weapon we want to use.

image with no caption

When to use composition

When we reference a whole family of behaviors like in the Unit class, we’re using composition. The Unit’s weapons property is composed of a particular Weapon implementation’s behavior. We can show this in UML like this:

image with no caption

Composition is most powerful when you want to use behavior defined in an interface, and then choose from a variety of implementations of that interface, at both compile time and run time.

Note

image with no caption

Composition allows you to use behavior from a family of other classes, and to change that behavior at runtime.

Pizza is actually a great example of composition: it’s composed of different ingredients, but you can swap out different ingredients without affecting the overall pizza slice.

[note from marketing: how will anyone take us seriously if we compare programming principles to pizza?]

When the pizza is gone, so are the ingredients...

There’s one important point we haven’t mentioned so far about composition. When an object is composed of other objects, and the owning object is destroyed, the objects that are part of the composition go away, too. That’s a little confusing, so let’s take a closer look at what that actually means.

Here’s our Unit class again, which has a composition relationship to the Weapon interface and its implementations:

image with no caption

Suppose we create a new Unit, and assign its weapon property to an instance of Sword:

image with no caption

What happens if this Unit is destroyed? Obviously, the pirate variable is trashed, but the instance of Sword referenced by pirate is also thrown away. It doesn’t exist outside of the pirate object.

image with no caption

In composition, the object composed of other behaviors owns those behaviors. When the object is destroyed, so are all of its behaviors.

The behaviors in a composition do not exist outside of the composition itself.

image with no caption

Brain Power

Can you think of an example where the ownership aspect of composition would be a negative in your application? When might you want the composed objects to exist outside of the composing class?

Aggregation: composition, without the abrupt ending

What happens when you want all the benefits of composition—flexibility in choosing a behavior, and adhering to the LSP—but your composed objects need to exist outside of your main object? That’s where aggregation comes in.

Note

image with no caption

Aggregation is when one class is used as part of another class, but still exists outside of that other class.

The ice cream, bananas, and cherries exist outside of a banana split. Take away that fancy container, and you’ve still got the individual components.

You’ve already used aggregation...

We’ve been using aggregation already, in Rick’s Stringed Instruments, from Chapter 5:

image with no caption

Aggregation versus composition

It’s easy to get confused about when you should use composition, and when you should use aggregation. The easiest way to figure this out is to ask yourself, Does the object whose behavior I want to use exist outside of the object that uses its behavior?

If the object does make sense existing on its own, then you should use aggregation; if not, then go with composition. But be careful! Sometimes the slightest change in the usage of your objects can make all the difference.

Inheritance is just one option

We started out this section talking about the LSP, and the basic idea that subclasses must be substitutable for their base classes. More importantly, though, now you have several ways to reuse behavior from other classes, beyond inheritance.

Let’s take a quick look back at our options for reusing behavior from other classes, without resorting to subclassing.

Delegation

image with no caption

Delegate behavior to another class when you don’t want to change the behavior, but it’s not your object’s responsibility to implement that behavior on its own.

Composition

image with no caption

You can reuse behavior from one or more classes, and in particular from a family of classes, with composition. Your object completely owns the composed objects, and they do not exist outside of their usage in your object.

If you favor delegation, composition, and aggregation over inheritance, your software will usually be more flexible, and easier to maintain, extend, and reuse.

Aggregation

image with no caption

When you want the benefits of composition, but you’re using behavior from an object that does exist outside of your object, use aggregation.

All three of these OO techniques allow you to reuse behavior without violating the LSP.

image with no caption
    Reset