Patterns working together

Chapter twelve of Head First Design Patterns is a sort of style exercise where lot of pattern are made working together. This is not what usually happens in real life, at least not at such extent, but it is interesting to check it out. I'd say that is better to read the book than this post, but it could give you a pale idea of what you could find there.

We have to write a duck simulator that manages ducks and anything that could sort of quacking. So, that means we are about to use the Adapter Patter, that helps us to adapt a sort-of-quacker (a goose) to react like a duck.

Then we want to add to the ducks a way to keep track of any time they quack, without modifying the duck classes. We'll use the Decorator Pattern for this.

The usage of decorator suggests us to create our ducks using the Abstract Factory Pattern, to avoid the risk of forgetting decorating a duck when creating it.

To keep simpler the management of our quacking animals we'll use the Composite Pattern, that helps us managing a flock of them as it would be a single quackable entity; besides we'll use the Iterator Pattern to go through all the elements in the composite.

Finally, we are about to use the Observer Pattern, to let a scientist (better known as a quackologist) to be notified any time a quackable animal quacks.

Let's start from the bottom. Our quacking being should be observable, that means it should implement an interface providing a way to the observer to register on it. And, simmetrically, we provide an interface that the observer has to implement, to be recognized by the observed entity and take an appropriate action when required:
public interface ObservableQuack {
    void register(Observer observer);
    void notifyObserver();
}

public interface Observer {
    void update(ObservableQuack duck);
}
We keep the class for the scientist as simple as possible:
public class Quackologist implements Observer {
    public void update(ObservableQuack duck) {
        System.out.println("Observed " + duck + " quacking.");
    }
}
It just puts string to the output console to acknowledge the duck quacking observation.

Our quacking friends implement a quackable interface, that would ask them to implement a quacking method, and all the methods required to be observable:
public interface Quackable extends ObservableQuack {
    public void quack();
}
Besides, we create an abstract class that would be the base class for each observable class, providing the standard implementation for the methods defined in the interface. That would avoid us to replicate the same code in many classes:
import java.util.ArrayList;

public abstract class Observable implements ObservableQuack {
    private ArrayList<Observer> observers = new ArrayList<Observer>();

    public void register(Observer observer) {
        observers.add(observer);
    }

    public void notifyObserver() {
        for(Observer o: observers)
            o.update(this);
    }
}
So, each duck instantiates a class that extends this Observable class and implement the Quackable interface:
public class DuckMallard extends Observable implements Quackable {
    public void quack() {
        System.out.println("Quack!");
        notifyObserver();
    }
}

// ...

public class DuckCall extends Observable implements Quackable {
    public void quack() {
        System.out.println("Kwak!");
        notifyObserver();
    }
}

// ...

public class DuckReadhead extends Observable implements Quackable {
    public void quack() {
        System.out.println("Quack!");
        notifyObserver();
    }
}

// ...

public class DuckRubber extends Observable implements Quackable {
    public void quack() {
        System.out.println("Squeak!");
        notifyObserver();
    }
}
We are about using the Duck classes wrapped in this decorator:
public class QuackCounter implements Quackable {
    Quackable duck;
    static int counter;

    public QuackCounter(Quackable duck) {
        this.duck = duck;
    }

    public void quack() {
        duck.quack();
        ++counter;
    }

    public static int getCounter() {
        return counter;
    }

    public void register(Observer observer) {
        duck.register(observer);
    }

    public void notifyObserver() {
        duck.notifyObserver();
    }
}
The QuackCounter redirects the calls it receives to the owned instance, we just add an increase in the counter any time we call the quack() method. To help the user not forgetting about using it, we make available a factory that the client would use to generate the ducks:
public abstract class AbstractDuckFactory {
    public abstract Quackable createDuckCall();
    public abstract Quackable createDuckMallard();
    public abstract Quackable createDuckReadhead();
    public abstract Quackable createDuckRubber();
}

// ...

public class CountingDuckFactory extends AbstractDuckFactory {
    @Override
    public Quackable createDuckCall() {
        return new QuackCounter(new DuckCall());
    }

    @Override
    public Quackable createDuckMallard() {
        return new QuackCounter(new DuckMallard());
    }

    @Override
    public Quackable createDuckReadhead() {
        return new QuackCounter(new DuckReadhead());
    }

    @Override
    public Quackable createDuckRubber() {
        return new QuackCounter(new DuckRubber());
    }
}
But what about our goose?
public class Goose {
    public void honk() {
        System.out.println("Honk!");
    }
}
We provide an adapter so that we could use it as it would be a duck:
public class GooseAdapter extends Observable implements Quackable {
    private Goose goose;

    public GooseAdapter(Goose goose) {
        this.goose = goose;
    }

    public void quack() {
        goose.honk();
        notifyObserver();
    }
}
And here is an aggregate, that help us to manage a flock of quackables in an easy way:
import java.util.ArrayList;

public class Flock extends Observable implements Quackable {
    private ArrayList<Quackable> flock = new ArrayList<Quackable>();

    public void add(Quackable element) {
        flock.add(element);
    }

    public void quack() {
        for(Quackable q: flock)
            q.quack();
    }

    @Override
    public void register(Observer observer) {
        for(Quackable q: flock)
            q.register(observer);
    }

    @Override
    public void notifyObserver() {
        for(Quackable q: flock)
            q.notifyObserver();
    }
}
Basically any method just delegates the behaviour to the elements of the flock.

And here is a little tester for our application:
public class Simulator {
    public static void main(String[] args) {
        AbstractDuckFactory factory = new CountingDuckFactory();
        new Simulator().simulate(factory);
    }

    private void simulate(AbstractDuckFactory factory) {
        System.out.println("Duck Simulator");

        Flock flock = new Flock();
        flock.add(factory.createDuckMallard());
        flock.add(factory.createDuckReadhead());
        flock.add(factory.createDuckCall());
        flock.add(factory.createDuckRubber());
        flock.add(new GooseAdapter(new Goose()));

        Quackologist quack = new Quackologist();
        flock.register(quack);

        simulate(flock);

        System.out.println("Number of quacks: " + QuackCounter.getCounter());
    }

    private void simulate(Quackable duck) {
        duck.quack();
    }
}

No comments:

Post a Comment