bboyjing's blog

HeadFirst设计模式十四【复合模式】

复合模式又叫模式的模式,就是说多个模式可以一个场景下携手合作,成为更强大的存在。本章将重访鸭子模拟器,来学习如何在一个解决方案中结合两个或多个模式,以解决一般或者重复发生的问题。下面我们将与鸭子重聚,从头重建鸭子模拟器,并通过使用一堆模式来赋予它一些有趣的能力。开工……

重现简单的鸭子模拟器场景

  1. 创建一个Quackable接口,所有鸭子都实现该接口

    1
    2
    3
    public interface Quackable {
    public void quack();
    }
  2. 创建鸭子类

    1
    2
    3
    4
    5
    6
    public class MallardDuck implements Quackable {
    @Override
    public void quack() {
    System.out.println("Quack");
    }
    }
    1
    2
    3
    4
    5
    6
    public class RedheadDuck implements Quackable {
    @Override
    public void quack() {
    System.out.println("Quack");
    }
    }
  3. 再假如几个不是真正的鸭子(鸭鸣器、橡皮鸭)

    1
    2
    3
    4
    5
    6
    public class DuckCall implements Quackable {
    @Override
    public void quack() {
    System.out.println("Kwak");
    }
    }
    1
    2
    3
    4
    5
    6
    public class RubberDuck implements Quackable {
    @Override
    public void quack() {
    System.out.println("Squeak");
    }
    }
  4. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Test {
    public static void main(String[] args) {
    Quackable mallardDuck = new MallardDuck();
    Quackable redheadDuck = new RedheadDuck();
    Quackable duckCall = new DuckCall();
    Quackable rubberDuck = new RubberDuck();
    System.out.println("Duck simulator");
    simulate(mallardDuck);
    simulate(redheadDuck);
    simulate(duckCall);
    simulate(rubberDuck);
    }
    static void simulate(Quackable duck) {
    duck.quack();
    }
    }

到这里,我们实现了最简单的鸭子模拟器的功能,只是通过实现Quackable接口来完成。下面会在这基础上逐步添加需求,同时我们会将之前学过的模式运用在其中。

有鸭子的地方可能也有鹅

只要有水塘的地方,就大概会有鸭子和鹅,如何让这个模拟器支持鹅叫呢。这个地方用到了适配器模式:

  1. 创建鹅,并且鹅的叫声不是Quack,而是Honk

    1
    2
    3
    4
    5
    public class Goose {
    public void honk() {
    System.out.println("Honk");
    }
    }
  2. 模拟器期望看到的是Quackable接口,那么我们创建适配器,将鹅适配成鸭子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class GooseAdapter implements Quackable {
    Goose goose;
    public GooseAdapter(Goose goose) {
    this.goose = goose;
    }
    @Override
    public void quack() {
    goose.honk();
    }
    }
  3. 测试,在模拟器中使用鹅

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class Test {
    public static void main(String[] args) {
    Quackable mallardDuck = new MallardDuck();
    Quackable redheadDuck = new RedheadDuck();
    Quackable duckCall = new DuckCall();
    Quackable rubberDuck = new RubberDuck();
    Quackable goose = new GooseAdapter(new Goose());
    System.out.println("Duck simulator");
    simulate(mallardDuck);
    simulate(redheadDuck);
    simulate(duckCall);
    simulate(rubberDuck);
    simulate(goose);
    }
    static void simulate(Quackable duck) {
    duck.quack();
    }
    }

统计呱呱叫的次数

这个需求是要在不改变鸭子类的情况下,统计呱呱叫的次数,书上用的是装饰者模式,我觉得代理模式也可以实现,我们一起来看下。

装饰者模式统计呱呱叫

  1. 创建装饰者,包装鸭子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class QuarckCounter implements Quackable {
    Quackable duck;
    static int numberOfQuacks;
    public QuarckCounter(Quackable duck) {
    this.duck = duck;
    }
    @Override
    public void quack() {
    duck.quack();
    numberOfQuacks++;
    }
    public static int getNumberOfQuacks() {
    return numberOfQuacks;
    }
    }
  2. 测试,但不计入鹅叫

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class Test {
    public static void main(String[] args) {
    Quackable mallardDuck = new QuarckCounter(new MallardDuck());
    Quackable redheadDuck = new QuarckCounter(new RedheadDuck());
    Quackable duckCall = new QuarckCounter(new DuckCall());
    Quackable rubberDuck = new QuarckCounter(new RubberDuck());
    // 不像计入鹅叫声,不去装饰就可以了
    Quackable goose = new GooseAdapter(new Goose());
    System.out.println("Duck simulator");
    simulate(mallardDuck);
    simulate(redheadDuck);
    simulate(duckCall);
    simulate(rubberDuck);
    simulate(goose);
    System.out.println("The ducks quacked " + QuarckCounter.getNumberOfQuacks() + " times.");
    }
    static void simulate(Quackable duck) {
    duck.quack();
    }
    }

代理模式统计呱呱叫

  1. 创建代理类,其中Test.numberOfQuacks在下面的测试类中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class QuackCounterHandler implements InvocationHandler {
    Quackable duck;
    public QuackCounterHandler(Quackable duck) {
    this.duck = duck;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (method.getName().equals("quack")) {
    Test.numberOfQuacks++;
    }
    return method.invoke(duck, args);
    }
    }
  2. 测试,但不计入鹅叫

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class Test {
    static int numberOfQuacks;
    public static void main(String[] args) {
    Quackable mallardDuck = duckProxy(new MallardDuck());
    Quackable redheadDuck = duckProxy(new RedheadDuck());
    Quackable duckCall = duckProxy(new DuckCall());
    Quackable rubberDuck = duckProxy(new RubberDuck());
    // 不像计入鹅叫声,不去装饰就可以了
    Quackable goose = new GooseAdapter(new Goose());
    System.out.println("Duck simulator");
    simulate(mallardDuck);
    simulate(redheadDuck);
    simulate(duckCall);
    simulate(rubberDuck);
    simulate(goose);
    System.out.println("The ducks quacked " + Test.numberOfQuacks + " times.");
    }
    static Quackable duckProxy(Quackable duck) {
    return (Quackable) Proxy.newProxyInstance(
    duck.getClass().getClassLoader(),
    duck.getClass().getInterfaces(),
    new QuackCounterHandler(duck)
    );
    }
    static void simulate(Quackable duck) {
    duck.quack();
    }
    }

两种方式都实现了在不改变鸭子类的情况下,统计了呱呱叫的次数。当然,代理模式是额外想出来的,下面还是假定我们使用的还是装饰者模式的版本。继续往下加需求。

优质地装饰鸭子

上面我们用装饰者模式来统计叫声的时候,没有把鹅叫算进去。也就是说装饰者模式下有包装才有效果,没包装就没有效果。为何不把需要包装的对象集中在一个地方创建呢?我们需要一些质量控制来确保鸭子一定是被包装起来的。需要建造一个工厂,创建装饰过的鸭子。此工厂应该生产各种不同类型的鸭子的产品家族,所以要用抽象工厂模式。

  1. 创建抽象工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public abstract class AbstractDuckFactory {
    public abstract Quackable createMallardDuck();
    public abstract Quackable createRedheadDuck();
    public abstract Quackable createDuckCall();
    public abstract Quackable createRubberDuck();
    }
  2. 先创建一个没有装饰过的正常鸭子工厂,该类虽然没用,但是可以作为抽象工厂模式完整性的展示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class DuckFactory extends AbstractDuckFactory {
    @Override
    public Quackable createMallardDuck() {
    return new MallardDuck();
    }
    @Override
    public Quackable createRedheadDuck() {
    return new RedheadDuck();
    }
    @Override
    public Quackable createDuckCall() {
    return new DuckCall();
    }
    @Override
    public Quackable createRubberDuck() {
    return new RubberDuck();
    }
    }
  3. 创建我们真正需要的工厂,装饰过的鸭子工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class CountingDuckFactory extends AbstractDuckFactory {
    @Override
    public Quackable createMallardDuck() {
    return new QuarckCounter(new MallardDuck());
    }
    @Override
    public Quackable createRedheadDuck() {
    return new QuarckCounter(new RedheadDuck());
    }
    @Override
    public Quackable createDuckCall() {
    return new QuarckCounter(new DuckCall());
    }
    @Override
    public Quackable createRubberDuck() {
    return new QuarckCounter(new RubberDuck());
    }
    }
  4. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class Test {
    public static void main(String[] args) {
    AbstractDuckFactory duckFactory = new CountingDuckFactory();
    Quackable mallardDuck = duckFactory.createMallardDuck();
    Quackable redheadDuck = duckFactory.createRedheadDuck();
    Quackable duckCall = duckFactory.createDuckCall();
    Quackable rubberDuck = duckFactory.createRubberDuck();
    Quackable goose = new GooseAdapter(new Goose());
    System.out.println("Duck simulator");
    simulate(mallardDuck);
    simulate(redheadDuck);
    simulate(duckCall);
    simulate(rubberDuck);
    simulate(goose);
    System.out.println("The ducks quacked " + QuarckCounter.getNumberOfQuacks() + " times.");
    }
    static void simulate(Quackable duck) {
    duck.quack();
    }
    }

管理一群鸭子

现在管理者想把鸭子视为一个集合,甚至是子集合来管理,目的可能是为了下一次命令,就能让整个集合的鸭子听从命令。还记得,组合模式允许我们像对待单个对象一样对待对象集合,下面来看看如何使用。

  1. 构建一群会鸭子,确切地说应该是一群Quackable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Flock implements Quackable {
    ArrayList<Quackable> quackers = new ArrayList<>();
    public void add(Quackable quacker) {
    quackers.add(quacker);
    }
    @Override
    public void quack() {
    Iterator<Quackable> iterator = quackers.iterator();
    while (iterator.hasNext()) {
    Quackable quackable = iterator.next();
    quackable.quack();
    }
    }
    }
  2. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class Test {
    public static void main(String[] args) {
    AbstractDuckFactory duckFactory = new CountingDuckFactory();
    Quackable redheadDuck = duckFactory.createRedheadDuck();
    Quackable duckCall = duckFactory.createDuckCall();
    Quackable rubberDuck = duckFactory.createRubberDuck();
    Quackable goose = new GooseAdapter(new Goose());
    System.out.println("\nDuck Simulator: With Composite - Flocks");
    Flock flockOfDucks = new Flock();
    flockOfDucks.add(redheadDuck);
    flockOfDucks.add(duckCall);
    flockOfDucks.add(rubberDuck);
    flockOfDucks.add(goose);
    Flock flockOfMallards = new Flock();
    Quackable mallardOne = duckFactory.createMallardDuck();
    Quackable mallardTwo = duckFactory.createMallardDuck();
    Quackable mallardThree = duckFactory.createMallardDuck();
    Quackable mallardFour = duckFactory.createMallardDuck();
    flockOfMallards.add(mallardOne);
    flockOfMallards.add(mallardTwo);
    flockOfMallards.add(mallardThree);
    flockOfMallards.add(mallardFour);
    flockOfDucks.add(flockOfMallards);
    System.out.println("\nDuck Simulator: Whole Flock Simulation");
    simulate(flockOfDucks);
    System.out.println("\nDuck Simulator: Mallard Flock Simulation");
    simulate(flockOfMallards);
    }
    static void simulate(Quackable duck) {
    duck.quack();
    }
    }

这个模式可以随意组合鸭子们,在某些场景下简单有效。

追踪个别鸭子

现在又出了一个需求,我们要追踪个别的鸭子,要有办法持续追踪它的实时呱呱叫,观察者模式正好派上用场了。

  1. 定义观察者接口,只有一个方法,就是更新被观察的对象

    1
    2
    3
    public interface Observer {
    public void update(QuackObservable duck);
    }
  2. 定义被观察者,用于注册和通知观察者。这个接口和上面的观察者可谓是你中有我,我中有你。

    1
    2
    3
    4
    5
    public interface QuackObservable {
    public void registerObserver(Observer observer);
    public void notifyObservers();
    }
  3. Quackable接口继承被观察者接口

    1
    2
    3
    public interface Quackable extends QuackObservable {
    public void quack();
    }
  4. 创建被观察者辅助类,代理所有被观察者的注册和通知观察者的请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class Observable implements QuackObservable {
    /**
    * 用于存放观察者
    */
    ArrayList<Observer> observers = new ArrayList<>();
    /**
    * 被观察者
    */
    QuackObservable duck;
    public Observable(QuackObservable duck) {
    this.duck = duck;
    }
    /**
    * 注册观察者
    *
    * @param observer
    */
    @Override
    public void registerObserver(Observer observer) {
    observers.add(observer);
    }
    /**
    * 通知观察者有变更
    */
    @Override
    public void notifyObservers() {
    Iterator<Observer> iterator = observers.iterator();
    while (iterator.hasNext()) {
    Observer observer = iterator.next();
    observer.update(duck);
    }
    }
    }
  5. 将被观察者辅助类组合到被观察者对象中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class MallardDuck implements Quackable {
    /**
    * 组合被观察者辅助类
    */
    Observable observable;
    public MallardDuck() {
    // 将被观察者本人传递给辅助类
    this.observable = new Observable(this);
    }
    @Override
    public void quack() {
    System.out.println("Quack");
    notifyObservers();
    }
    /**
    * 将注册观察者委托给辅助类
    *
    * @param observer
    */
    @Override
    public void registerObserver(Observer observer) {
    observable.registerObserver(observer);
    }
    /**
    * 将通知观察者委托给辅助类
    */
    @Override
    public void notifyObservers() {
    observable.notifyObservers();
    }
    }
  6. 实现监听呱呱叫的观察者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Quackologist implements Observer {
    /**
    * 接收到呱呱叫通知
    *
    * @param duck
    */
    @Override
    public void update(QuackObservable duck) {
    System.out.println("Quackologist:" + duck + " just quack.");
    }
    }
  7. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Test {
    public static void main(String[] args) {
    System.out.println("\nDuck Simulator: With Observer");
    Quackologist quackologist = new Quackologist();
    Quackable mallardDuck = new MallardDuck();
    mallardDuck.registerObserver(quackologist);
    simulate(mallardDuck);
    }
    static void simulate(Quackable duck) {
    duck.quack();
    }
    }

复合模式的第一部分就学到这里,这一章节我们把之前学过的一些设计模式复习了一遍,并且知道了模式之间是可以相互合作的。总之,我们要学会的是在合适场景下选择合适的方式去解决问题。下一章节将继续学习通过复新产生的一种新模式:MVC模式。