Head First 设计模式 —— 14. 复合 (Compound) 模式
2016 年 5 月 26 日
复合模式
在一个解决方案中结合两个或多个模式,以解决一般或重复发生的问题。 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