2

Head First 设计模式 —— 14. 复合 (Compound) 模式

 3 years ago
source link: https://segmentfault.com/a/1190000039003880
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

复合模式

在一个解决方案中结合两个或多个模式,以解决一般或重复发生的问题。 P500

思考题

public interface Quackable {
    public void quack();
}

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

public class Goose {
    public void honk() {
        System.out.println("Honk");
    }
}

假设我们想要在所有使用鸭子的地方使用鹅,毕竟鹅会叫、会飞、会游,和鸭子差不多。什么模式可以让我们轻易地将鸭子和鹅掺杂在一起呢? P503

  • 适配器模式。题目需要轻易地将一种行为转换为另一种行为,且不要改变原有的类,所以需要使用适配器转换。

思考题

我们要如何在不变化鸭子类的情况下,计算所有鸭子呱呱叫的总次数呢?有没有什么模式可以帮上忙? P505

  • 装饰器模式。题目要求增加新的行为,且不改变原有类,所以可以使用装饰器。
  • 代理模式。代理模式会控制访问,而鹅经适配器后转换的行为不应该被统计,所以可以通过代理模式进行控制。

思考题

你能够为鹅写一个抽象工厂吗?创建”内鹅外鸭“的对象时,你怎么处理? P511

  • 新建一个工厂,专门创建被适配器转换成鸭子的鹅

    public abstract class AbstractGooseDuckFactory {
        public abstract Quackable createGooseDuck();
    }
    
    public class GooseDuckFactory extends AbstractGooseDuckFactory {
        public Quackable createGooseDuck() {
            return new GooseAdapter(new Goose());
        }
    }

思考题

我们需要将鸭子视为一个集合,甚至是子集合(subcollection),如果我们下一次命令,就能让整个集合的鸭子听命行事,那就太好了。什么模式可以帮我们? P512

  • 迭代器模式。由于我们需要将鸭子视为一个集合,可以遍历执行同一操作,所以可以使用迭代器模式方便遍历。
  • 组合模式。由于鸭子集合可能会含有子集合和鸭子,并也需要支持上述行为,所以可以使用组合模式将鸭子和鸭子集合统一起来。

思考题

你能够有办法持续追踪个别鸭子的实时呱呱叫吗? P516

  • 观察者模式。题目意思就是鸭子在呱呱叫时通知观察人员,所以鸭子是可被观察的,应该继承 Observable 类,而观察人员应该实现 Observer 接口 ,观察人员在个别鸭子上注册以便实时接收鸭子的呱呱叫行为。

    • 按照以上设计会修改所有的鸭子类,所以就想到可以再加一个装饰器继承 Observable 类,并实现 Quackable 接口,这样改动量最小,不会改变原有鸭子类,也可以将鸭子和可被观察解耦。但想象很美好,一去实现就会遇到很多问题:用户代码必须与该装饰器耦合,需要特判该装饰器以执行注册观察者和通知观察者的方法;该装饰器只能最后包装,如果被其他装饰器包装就无法再调用相应方法;不便于将相应的方法扩展到组合模式中的集合上。所以还是需要接口上的修改,改变所有鸭子的行为。
    • 书上设计是让 Quackable 接口继承 QuackObservable 接口以便所有能叫的鸭子都能被观察;修改所有鸭子类,并将 Observable 类组合进鸭子类中,将注册观察者和通知观察者的方法内部委托到 Observable 相应的方法中;同时也要修改相应的装饰器。

思考题

我们还没有改变一个 Quackable 的实现,即 QuackCounter 装饰器。它也必须成为 Observable 。你何不试着写出它的代码呢? P518

public class QuackCounter implements Quackable {
    Quackable duck;
    static int numberOfQuacks;
    
    public QuackCounter(Quackable duck) {
        this.duck = duck;
    }
    
    public void quack() {
        duck.quack();
        ++numberOfQuacks;
    }
    
    public static int getQuacks() {
        return numberOfQuacks;
    }
    
    public void registerObserver(Observer observer) {
        duck.registerObserver(observer);
    }
    
    public void notifyObservers() {
        duck.notifyObservers();
    }
}

思考题

万一呱呱叫学家想观察整个群,又该怎么办呢?当观察某个组合时,就等于观察组合内的每个东西。 P520

public class Flock implements Quackable {
    ArrayList ducks = new ArrayList();
    
    public void add(Quackable duck) {
        ducks.add(duck);
    }
    
    public void quack() {
        Iterator iterator = ducks.iterator();
        while(iterator.hasNext()) {
            Quackable duck = (Quackable) iterator.next();
            duck.quack();
        }
    }
    
    public void registerObserver(Observer observer) {
        Iterator iterator = ducks.iterator();
        while(iterator.hasNext()) {
            Quackable duck = (Quackable) iterator.next();
            duck.registerObserver(observer);
        }
    }
    
    public void notifyObservers() {
        // 鸭群注册观察者都委托到孩子上了,所以通知观察者的事情并不需要鸭群做任何事
    }
}

所思所想

  • 可以通过让原有接口继承新接口的方式,再增加接口方法和相应的功能的同时,减少用户修改代码。例如:JDK7 中就让原有的 Closable 接口继承 AutoClosable 接口,使得原有的用户代码都不必修改就能在 JDK7中使用带资源的 try 语句能自动关闭资源的新特性。(第一次看见 AutoClosable 接口时,直接从语义上就认为 AutoClosable 继承了 Closable ,没想到正相反)

本文首发于公众号:满赋诸机( 点击查看原文 ) 开源在 GitHub : reading-notes/head-first-design-patterns

qieIFjj.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK