A Live Developer Journal

Flying rubber ducks - head first design pattern notes

Notes are based on Head First Design Patterns

Ducks are ruining everything

You have a duck class that has three methods: Quack, swim and display. The display method is concerned with what the duck looks like. Lots of different ducks inherit from this super class, each of them look a little different (they all have a display method, but the code that describes how they look inside the method is different for each duck).

If you add a 'fly' method to the duck class that all other ducks inherit from(super class), then they will all be able to fly. However, you might create a rubber duck. The problem with this is that rubber ducks can't fly. They also squeak instead of quack. So inside the rubber duck class, you override the default fly method so that it does nothing, and you override the quack class so the duck 'squeaks' instead. If you have a wooden toy duck, you override both the quack and fly methods so that they do nothing. This isn't a very good approach, it isn't maintainable.

A better solution would be to create an interface called Flyable that has a fly method. This class inherits from the main duck superclass, so can do all the other things normal ducks can do. We also create a Quackable interface with the method quack, which also inherits from the main duck superclass. Then whenever we need a duck that can fly, it inherits from the Flyable interface. Whenever we need a duck that can quack, it inherits from the Quakable interface. Whenever we need a duck that can swim and have an appearance (display) and nothing else, it inherits from the Duck superclass. We don't have to override anything.

The problem with this approach is that instead of overriding each behaviour you don't want your duck to have, you are instead creating a new interface for each of these behaviours. If you want to change exactly how a duck flies, you have to tweak them in all of the flying duck subclasses.

Interfaces are different to classes in that they do not contain any code inside of the methods they have. They have a bunch of methods that become a requirement for each of the subclasses that inherit from them. Like a contract. If you inherit from the flying duck class you are signing a contract that says you will also have a fly method.

The example above highlights that there are a lot of maintainance issues to think about when writing code. The one thing you can always count on is that the software that you write is always going to change. So you need to think about how to make your code easy to maintain and extend in the face of constant, and often unexpected changes.

Exercise: Reasons for change

An exercise in the book tells you to list reasons why you have had to change code in your applications. I'm going to give some examples from my current work place.

Separating what changes from what stays the same

A design principle for dealing with the duck situation is to identify which parts of your application vary and seperate them from the parts that stay the same.

This design principle is simple, but underlies almost every design pattern. They all provide a way to let some part of a system vary independently of all other parts

A lot of the time, you won't really know what part of your code is going to change until you have to change a behaviour a few different times when the requirements change. This is a good time to pull out this behaviour and separate them.

Designing the classes that implement the duck and class behaviours

To seperate these behaviours [fly and quack] from the duck class, we'll pull both methods out of the duck class and create a new set of classes to represent each behaviour.

Program to an interface, not an implementation

In order to make the changes listed above, we can look to a second design principle for help, which is to program to an interface and not to an implementation.

The benefit of this approach is that Duck classes won't need to know how to implement their fly behaviours. While it might have a specific fly type, it won't know exactly how to fly like that. Like a plugginable superpower.

The differece between this way of doing things and the first set of approaches is that we are no longer tied to a concrete implementation of a behaviour. If we wanted to change a flying behavior, we would have to modify or write more code. This way, the behavior can be swapped out by any of the Fly behaviours at any time during runtime.

Another good example of programming to an interface is as follows:

From this:


Dog d = new Dog();
d.bark

To this:


Animal animal = new Dog();
animal.makeSound();

In the first example, we are locked into using a dog class who knows how to bark. In the second example, we can use any kind of animal who can make any sound. We are still using the dog class who barks, but the dog can easily be changed for another animal.

We can improve this code even further by assigning the concrete dog class at runtime instead of hardcoding it.


a = getAnimal();
a.makeSound();

Implementing Duck behaviours

The key is that the Duck will now delegate its flying and quacking behaviour, instead of using quacking and flying methods defined in the Duck class (or subclass)

The problem with this is that we are still referencing a concrete behaviour class in our quackBehaviour instance class, which is something we are only doing temporarily. We are shown how to fix this later in the book.

A great benefit of this approach is that other classes can use the fly behaviour too, even if they are not ducks. The fly behaviours are no longer locked into the duck classes only.

Duck.java


public abstract class Duck {
  FlyBehaviour flyBehaviour;
  QuackBehaviour quackBehaviour;
  public Duck() {
  }

  public abstract void display();

  public void performFly() {
    flyBehaviour.fly();
  }

  public void performQuack() {
    quackBehaviour.quack();
  }

  public void swim() {
    System.out.println("All ducks float, even decoys!);
  }

  public void setFlyBehaviour(FlyBehaviour fb) {
    flyBehaviour = fb;
  }

  public void setQuackBehaviour(QuackBehaviour qb) {
    quackBehaviour qb;
  }
}

MallardDuck.java


public class MallardDuck extends Duck {

  public MallardDuck() {
    quackBehaviour = new Quack();
    flyBehaviour = new FlyWithWings();
  }

  public void display() {
    System.out.println("I'm a real Mallard duck");
  }
}

ModelDuck.java


public class ModolDuck extends Duck {
  public ModelDuck() {
    flyBehaviour = new FlyNoWay();
    quackBehaviour = new Quack();
  }

  public void display() {
    System.out.println("I'm a model duck");
  }
}

FlyBehaviour.java


public interface FlyBehaviour {
  public void fly():
}

FlyWithWings.java


public class FlyWithWings implements FlyBehaviour {
  public void fly() {
    System.out.println("I'm flying!!");
  }
}

FlyNoWay.java


public class FlyNoWay implements FlyBehaviour {
  public void fly() {
    System.out.printlin("I can't fly");
  }
}

FlyRocketPowered.java


public class FlyRocketPowered implements FlyBehaviour {
  public void fly() {
    System.out.println("I'm flying with a rocket!");
  }
}

QuackBehaviour.java


public interface QuackBehaviour {
  public void quack() {
    System.out.println("Quack");
  }
}

Quack.java


public class Quack implements QuackBehaviour {
  public void quack() {
    System.out.println("Quack");
  }
}

MuteQuack.java


public class MuteQuack implements QuackBehaviour {
  public void quack() {
    System.out.println("<< Silence >>");
  }
}

Squeak.java


public class Squeak implements QuackBehaviour {
  public void quack() {
    System.out.println("Squeak");
  }
}

MiniDuckSimulator.java


public class MiniDuckSimulator {
  public static void main(String[] args) {
    Duck mallard = new MallardDuck();
    mallard.performQuack();
    mallard.performFly();
  }
}

MiniDuckSimulator.java - model duck edition


public class MiniDuckSimulator {
  public static void main(String[] args) {
    Duck mallard = new MallardDuck();
    mallard.performQuack();
    mallard.performFly();

    Duck model = new ModelDuck();

    model.performFly();

    model.setFlyBehaviour(new FlyRocketPowered());

    model.performFly();
  }
}

Output from above would be 'Quack', 'I'm flying!!', 'I can't fly' and 'I'm flying with a rocket!'

Encapsulated behaviours

Instead of thinking of the duck behaviours as a set of behaviours, we can start thinking of them as a family of algorithms. The algorithms represents things that a duck would do (different ways of quacking or flying), but we can also apply the same techniques to a set of classes that implement currency conversion.

Exercise: Write the appropriate relationship

The exercise is to write the appropriate relationships (IS-A, HAS-A and IMPLEMENTS) on each arrow in the class digram in the book

When you put two classes together like this you are using composition. Instead of inheriting their behaviour, the ducks get their behaviour by being composed with the right behaviour object.

Favour composition over inheritance

Composing objects with the right behaviour object is our third design principle, which is to favour composition over inheritance.

Benefits of composition

Composition is used in many design patterns.

We always spend more time maintaining and changing sofware than on initial development

MY FIRST DESIGN PATTERN YAYYYYYY! The Strategy Pattern

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Exercise: Action Adventure game

Below you'll find a mess of classes and interfaces for an action adventure game. You'll find classes for game characters along with classes for weapon behaviours the characters can use in the game. Each character can make use of one weapon at a time, but can change weapons at any time during the game, your job is to sort it all out.

  1. 1. Arrange the classes
  2. 2. Identify one abstract class (A x 1), one interface (I x 1), and 8 classes (C x 8)
  3. Draw arrows between classes: ---|> = extends, ......> = implements, ----> = HAS-A.
  4. Put the method SetWeapon into the right class

The following method belongs to the Character class.


setWeapon(WeaponBehaviour w) {
  this.weapon = w;
}

Design patterns are a shared vocabulary

Design patterns give you a way to communicate with other developes. It also elevates your thinking about architectures by letting you think at the pattern level, not the nitty-gritty object level.

Benefits of design patterns

How to use design patterns

Tools for your design toolbox

Object-oriented Basics

Object-oriented Principles

As you learn more patterns, think about how they rely on oo basics and principles - will try this out for the strategy pattern I just learned.

How does the Strategy pattern rely on Object-oriented basics?

How does the Strategy pattern rely on Object-oriented design principles?