本文代码较多且综合了好几种设计模式使用,建议收藏或者慢慢观看。
本文将综合抽象工厂、组合模式、装饰器模式、适配器模式、观察者模式等模式运用在本例子中。对于不熟悉这些模式的读者可以阅读历史文章学习加强自己的代码心法。完整代码在 github:https://github.com/UniqueDong/zero-design-patterns 对应的 com.zero.headfirst.verb 包目录下。
适配器模式
首先我们从制造一个屏幕模拟器开始,模拟鸭子叫,根据需求我们先定义 Quackable
接口,然后分别定义MallarDuck、RedheadDuck、RubberDuck、DuckCall
等不同的鸭子实现呱呱叫接口,这个时候有鸭子出现的地方也出现了鹅混在其中,我们就通过适配器模式适配成鸭子。
/** * 鸭子呱呱叫接口 */ public interface Quackable { /** * 呱呱叫 */ void quack(); } /** * 鸭子叫玩具 */ public class DuckCall implements Quackable { @Override public void quack() { System.out.println("鸭子模拟器叫..."); } } public class MallarDuck implements Quackable { @Override public void quack() { System.out.println("标准绿头鸭呱呱叫..."); } } public class RedheadDuck implements Quackable { @Override public void quack() { System.out.println("红头鸭呱呱叫..."); } } public class RubberDuck implements Quackable { @Override public void quack() { System.out.println("橡皮鸭吱吱叫..."); } }
现在各种鸭子定义好了,我们定义鹅 Goose
/** * 天鹅,假装鸭子在模拟器中出现 */ public class Goose { public void honk() { System.out.println("天鹅叫声,额"); } }
这个时候需要适配器将鹅适配成鸭子,适配器持有鹅的引用,鹅就是被适配对象,同时适配器实现 Quackable
接口,当调用 quack()
的时候实际是委托调用了honk()
方法。
/** * 将天鹅适配成鸭子 */ public class GooseAdapter implements Quackable { /** * 持有被修饰的对象 */ private Goose goose; public GooseAdapter(Goose goose) { this.goose = goose; } @Override public void quack() { goose.honk(); } }
最后我们的屏幕模拟器登场,展示满屏呱呱叫的鸭子与鹅。
public class DuckSimulation { public static void main(String[] args) { DuckSimulation simulation = new DuckSimulation(); simulation.simulate(); } /** * 模拟屏幕展示功能 */ private void simulate() { Quackable redheadDuck = new RedheadDuck(); Quackable mallarDuck = new MallarDuck(); Quackable rubberDuck = new RubberDuck(); Quackable duckCall = new DuckCall(); GooseAdapter gooseAdapter = new GooseAdapter(new Goose()); simulate(redheadDuck); simulate(mallarDuck); simulate(rubberDuck); simulate(duckCall); simulate(gooseAdapter); } private void simulate(Quackable quackable) { quackable.quack(); } }
装饰器-统计鸭子叫的次数
现在继续新增需求,在鸭子类不修改的情况下我们要统计叫的次数。我们创建一个装饰者,通过把鸭子包装进装饰者对象,然后给鸭子新的功能(计算叫的次数)。装饰器也要实现 Quackable
接口,并且持有鸭子实例变量-被装饰者,内部使用一个静态变量保存叫的次数,当 quack()
被调用的时候我们就把调用委托给被装饰的 Quackable
对象,并且把叫的次数加 1。
/** * 装饰器模式,发出叫声的时候同时记录次数.这样我们就不必修改每个鸭子发出声音的方法 */ public class QuackCounter implements Quackable { private Quackable quack; private static AtomicInteger atomicInteger = new AtomicInteger(0); public QuackCounter(Quackable quack) { this.quack = quack; } @Override public void quack() { quack.quack(); atomicInteger.incrementAndGet(); } public static AtomicInteger getQuacks() { return atomicInteger; } }
利用装饰器模式-我们实现了不修改鸭子类却又给鸭子新增了功能。接着我们需要修改屏幕模拟器,将需要统计叫声的鸭子包装在装饰器中。
public class DuckSimulation { public static void main(String[] args) { DuckSimulation simulation = new DuckSimulation(); simulation.simulate(); } /** * 模拟屏幕展示功能 */ private void simulate() { // 使用装饰器包装鸭子 Quackable redheadDuck = new QuackCounter(new RedheadDuck()); Quackable mallarDuck = new QuackCounter(new MallarDuck()); Quackable rubberDuck = new QuackCounter(new RubberDuck()); Quackable duckCall = new QuackCounter(new DuckCall()); //不想把天鹅的叫声统计,所以不用装饰器装饰天鹅 GooseAdapter gooseAdapter = new GooseAdapter(new Goose()); System.out.println("使用装饰器模式后,统计叫的次数,不包含天鹅"); simulate(redheadDuck); simulate(mallarDuck); simulate(rubberDuck); simulate(duckCall); simulate(gooseAdapter); System.out.println("一共叫了 " QuackCounter.getQuacks() " 次"); } private void simulate(Quackable quackable) { quackable.quack(); } }
抽象工厂
写到这里我们已经用上了适配器模式、装饰器模式。有没有觉得我们创建鸭子都是 new 出来的?为什么不把创建鸭子的的程序集合集中在一个地方,换句话说就是将创建于修饰的部分包装起来。就是我们接下来要说的:工厂模式。
我们定义一个工厂,生产各种不同类型的鸭子产品家族,所以我们使用抽象工厂模式。
首先从定义抽象工厂 AbstractDuckFactory
开始,用于创建不同类型的鸭子家族。
/** * 抽象工厂模式,定义产品族 */ public abstract class AbstractDuckFactory { public abstract Quackable createDuckCall(); public abstract Quackable createMallarDuck(); public abstract Quackable createRedheadDuck(); public abstract Quackable createRubberDuck(); }
接着创建一个普通鸭子工厂继承抽象工厂,该工厂创建没有装饰器装饰的鸭子。屏幕模拟器并不知道实际的产品是什么,只知道它实现了 Quackable
接口。把创建细节丢给工厂,而不是直接 new 创建,这也是控制反转的思想,在 Spring 框架中大量出现。
/** * 每个方法创建一种产品,一种特定种类的 Quackable */ public class DuckFactory extends AbstractDuckFactory { @Override public Quackable createDuckCall() { return new DuckCall(); } @Override public Quackable createMallarDuck() { return new MallarDuck(); } @Override public Quackable createRedheadDuck() { return new RedheadDuck(); } @Override public Quackable createRubberDuck() { return new RubberDuck(); } }
现在我们要创建带计数器叫声功能的鸭子工厂DuckCountFactory
,它持有DuckFactory
的一个实例用于创建普通鸭子并放进 QuackCounter
装饰器中从而得以创建带计数器的鸭子。这里其实也是用到了装饰器模式。
/** * 叫声计数器工厂:同时还结合了装饰器模式,持有 工厂引用,包装了duckFactory,从而增强了功能 * 创建计数器鸭子 */ public class DuckCountFactory extends AbstractDuckFactory { private AbstractDuckFactory duckFactory; public DuckCountFactory(DuckFactory duckFactory) { this.duckFactory = duckFactory; } @Override public Quackable createDuckCall() { return new QuackCounter(duckFactory.createDuckCall()); } @Override public Quackable createMallarDuck() { return new QuackCounter(duckFactory.createMallarDuck()); } @Override public Quackable createRedheadDuck() { return new QuackCounter(duckFactory.createRedheadDuck()); } @Override public Quackable createRubberDuck() { return new QuackCounter(duckFactory.createRubberDuck()); } }
既然工厂我们定义好了,接下来就要修改屏幕模拟器代码运用工厂来产生鸭子。我们创建一个多态方法simulate()
,此方法需要一个用来创建对象的工厂,传入不同的工厂则生产不同的产品家族。
我们把原来直接 new 产生鸭子的代码改造成通过工厂创建,代码如下。
public class DuckSimulation { public static void main(String[] args) { DuckSimulation simulation = new DuckSimulation(); AbstractDuckFactory duckCountFactory = new DuckCountFactory(new DuckFactory()); simulation.simulate(duckCountFactory); } /** * 模拟屏幕展示功能 */ private void simulate(AbstractDuckFactory duckFactory) { Quackable redheadDuck = duckFactory.createRedheadDuck(); Quackable mallarDuck = duckFactory.createMallarDuck(); Quackable rubberDuck = duckFactory.createRubberDuck(); Quackable duckCall = duckFactory.createDuckCall(); //不想把天鹅的叫声统计,所以不用装饰器装饰 GooseAdapter gooseAdapter = new GooseAdapter(new Goose()); System.out.println("使用装饰器模式后,统计叫的次数,不包含天鹅,同时使用了工厂模式产生鸭子"); simulate(redheadDuck); simulate(mallarDuck); simulate(rubberDuck); simulate(duckCall); simulate(gooseAdapter); System.out.println("一共叫了 " QuackCounter.getQuacks() " 次"); } private void simulate(Quackable quackable) { quackable.quack(); } }
组合模式-管理一群鸭子
需求方又来了,他们想控制不同鸭子群,把鸭子视为一个集合,为了满足能够管理不同鸭群的需求。
还记得么?组合模式允许我们像对待单个对象一样对待集合对象。所以利用组合模式管理一群Quackable
再适合不过了。
首先,组合需要和叶子节点元素一样实现相同的接口。这里的「叶节点」就是Quackable
。
我们用一个 ArrayList
保存属于这个组合的Quackable
对象,用add()
方法新增Quackable
对象到组合中。因为鸭群也是Quackable
,所以也具备quack()
方法,该方法会对整个鸭群产生作用。
/** * 组合模式,管理一群鸭子: 对待单个对象一样对待集合对象 * 组合需要和叶子节点一样实现相同的接口,这里的叶子节点就是 Quackable */ public class Flock implements Quackable { private List<Quackable> quackers = new ArrayList<>(); public void add(Quackable quackable) { quackers.add(quackable); } @Override public void quack() { for (Quackable quackable : quackers) { quackable.quack(); } } }
我们的组合好了,现在要修改模拟器实现鸭群定义与管理,和之前一样。
- 首先创建所有的
Quackable
对象。 - 在创建不同的组合
Flock
鸭群,然后将对应的鸭子放入鸭群。 - 最后就能根据组合的鸭群分类测试鸭子满屏飞以及控制不同的鸭群行为了。
public class DuckSimulation { public static void main(String[] args) { DuckSimulation simulation = new DuckSimulation(); AbstractDuckFactory duckCountFactory = new DuckCountFactory(new DuckFactory()); simulation.simulate(duckCountFactory); } /** * 模拟屏幕展示功能 */ private void simulate(AbstractDuckFactory duckFactory) { Quackable redheadDuck = duckFactory.createRedheadDuck(); Quackable mallarDuck = duckFactory.createMallarDuck(); Quackable rubberDuck = duckFactory.createRubberDuck(); Quackable duckCall = duckFactory.createDuckCall(); //不想把天鹅的叫声统计,所以不用装饰器装饰 GooseAdapter gooseAdapter = new GooseAdapter(new Goose()); System.out.println("----使用装饰器模式后,统计叫的次数,不包含天鹅,同时使用了工厂模式产生鸭子---"); System.out.println("--使用组合模式管理鸭子群--"); // 主要鸭子群 Flock flockOfDucks = new Flock(); flockOfDucks.add(redheadDuck); flockOfDucks.add(mallarDuck); flockOfDucks.add(rubberDuck); flockOfDucks.add(duckCall); flockOfDucks.add(gooseAdapter); // 绿头鸭群 Flock mallarFlock = new Flock(); Quackable mallarDuck1 = duckFactory.createMallarDuck(); Quackable mallarDuck2 = duckFactory.createMallarDuck(); Quackable mallarDuck3 = duckFactory.createMallarDuck(); Quackable mallarDuck4 = duckFactory.createMallarDuck(); mallarFlock.add(mallarDuck1); mallarFlock.add(mallarDuck2); mallarFlock.add(mallarDuck3); mallarFlock.add(mallarDuck4); System.out.println("----主要鸭子群模拟器----"); simulate(flockOfDucks); System.out.println("---绿头鸭群模拟---"); simulate(mallarFlock); System.out.println("一共叫了 " QuackCounter.getQuacks() " 次"); } private void simulate(Quackable quackable) { quackable.quack(); } }
观察者模式-观察特定鸭子叫
组合模式让我们很好的管理鸭群,但是现在产品经理又有一个相反的需求:我们也需要追踪个别鸭子,当它呱呱叫鹅时候我们能够收到通知。
同学们是不是想到观察和模式,当感兴趣的某个事件发生的时候我们就收到通知。就像我们订阅的公众号发送消息,那么就通知。
被观察者QuackObservable
首先我们需要定义被观察者角色,提供「注册观察者」、「移除观察者」、「通知观察者」方法。任何想被观察的Quackable
都要实现该接口,任何注册到QuackObservable
的观察者QuackObserver
都会收到呱呱叫通知,晒后我们会定义观察者。
/** * 被观察者:需要管理观察者与通知观察者 */ public interface QuackObservable { /** * 注册观察者 * @param observer */ void registerObserver(QuackObserver observer); /** * 移除观察者 * @param observer */ void removeObserver(QuackObserver observer); /** * 通知观察者 */ void notifyObservers(); }
现在我们需要把所有鸭子实现该接口成为被观察者,从而使得我们可以观察呱呱叫。所以我们干脆让Quackable
来继承QuackObservable
接口。我们必须确认所有实现Quackable
的具体类能够扮演被观察者角色。
/** * 呱呱叫接口,继承被观察者接口,让所有实现了 Quackable 的实现类能够扮演被观察者 */ public interface Quackable extends QuackObservable { /** * 呱呱叫 */ void quack(); }
ObservableDelegate
辅助类
由于所有的鸭子都实现了Quackable
接口,而该接口又继承了QuackObservable
被观察者接口。所以每个鸭子类都需要实现注册、通知、取消注册的代码。我们不这么干,抽出一个辅助类ObservableDelegate
封装「注册」、「通知」、「取消注册」功能,然后和QuackObservable
组合在一起,这样只需要一份代码即可,QuackObservable
的所有调用都委托到ObservableDelegate
辅助类。
在构造方法中我们传入QuackObservable
,好让观察者知道是哪个对象在呱呱叫。
/** * 实现被观察者接口,与 QuackObservable 组合在一起,这样我们只需要一份代码,QuackObservable 的所有调用都委托给 * Observable 辅助类 */ public class ObservableDelegate implements QuackObservable { private List<QuackObserver> observerList = new ArrayList<>(); private QuackObservable duck; private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public ObservableDelegate(QuackObservable duck) { this.duck = duck; } @Override public void registerObserver(QuackObserver observer) { ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock(); writeLock.lock(); try { observerList.add(observer); } finally { writeLock.unlock(); } } @Override public void removeObserver(QuackObserver observer) { ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock(); writeLock.lock(); try { observerList.remove(observer); } finally { writeLock.unlock(); } } @Override public void notifyObservers() { ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock(); readLock.lock(); try { //通知所有的观察者 observerList.forEach(item -> item.update(duck)); } finally { readLock.unlock(); } } }
辅助类实现了所有必要的功能,我们只要把它插进一个类就可以将工作委托到 ObservableDelegate。
接下来我们整合 ObservableDelegate
和 Quackable
使得 Quackable
的注册于通知都委托到辅助类。所以每个Quackable
的实现类都应该持有ObservableDelegate
的实例。接下来我们改造鸭子实现类。
public class MallarDuck implements Quackable { private QuackObservable observableDelegate; public MallarDuck() { this.observableDelegate = new ObservableDelegate(this); } @Override public void quack() { System.out.println("标准绿头鸭呱呱叫..."); //当呱呱叫的时候让观察者知道 notifyObservers(); } @Override public void registerObserver(QuackObserver observer) { observableDelegate.registerObserver(observer); } @Override public void removeObserver(QuackObserver observer) { observableDelegate.removeObserver(observer); } @Override public void notifyObservers() { observableDelegate.notifyObservers(); } } /** * 鸭子叫模拟器 */ public class DuckCall implements Quackable { /** * 持有 QuackObservable 的引用,将 委托给辅助类实现 */ private QuackObservable observableDelegate; public DuckCall() { // 委托给观察者辅助类 this.observableDelegate = new ObservableDelegate(this); } @Override public void quack() { System.out.println("鸭子模拟器叫..."); //当呱呱叫的时候让观察者知道 notifyObservers(); } @Override public void registerObserver(QuackObserver observer) { observableDelegate.registerObserver(observer); } @Override public void removeObserver(QuackObserver observer) { observableDelegate.removeObserver(observer); } @Override public void notifyObservers() { observableDelegate.notifyObservers(); } } public class RedheadDuck implements Quackable { private QuackObservable observableDelegate; public RedheadDuck() { this.observableDelegate = new ObservableDelegate(this); } @Override public void quack() { System.out.println("红头鸭呱呱叫..."); //当呱呱叫的时候让观察者知道 notifyObservers(); } @Override public void registerObserver(QuackObserver observer) { observableDelegate.registerObserver(observer); } @Override public void removeObserver(QuackObserver observer) { observableDelegate.removeObserver(observer); } @Override public void notifyObservers() { observableDelegate.notifyObservers(); } } /** * 橡皮鸭 */ public class RubberDuck implements Quackable { private QuackObservable observableDelegate; public RubberDuck() { this.observableDelegate = new ObservableDelegate(this); } @Override public void quack() { System.out.println("橡皮鸭吱吱叫...");//当呱呱叫的时候让观察者知道 notifyObservers(); } @Override public void registerObserver(QuackObserver observer) { observableDelegate.registerObserver(observer); } @Override public void removeObserver(QuackObserver observer) { observableDelegate.removeObserver(observer); } @Override public void notifyObservers() { observableDelegate.notifyObservers(); } }
还有我们的适配器、以及鸭子叫计数器也是都把被观察者核心代码功能调用委托给辅助类
/** * 将天鹅适配成鸭子 */ public class GooseAdapter implements Quackable { /** * 持有被修饰的对象 */ private Goose goose; private QuackObservable observableDelegate; public GooseAdapter(Goose goose) { this.goose = goose; observableDelegate = new ObservableDelegate(this); } @Override public void quack() { goose.honk(); //当呱呱叫的时候让观察者知道 notifyObservers(); } @Override public void registerObserver(QuackObserver observer) { observableDelegate.registerObserver(observer); } @Override public void removeObserver(QuackObserver observer) { observableDelegate.removeObserver(observer); } @Override public void notifyObservers() { observableDelegate.notifyObservers(); } } /** * 装饰器模式,发出叫声的时候同时记录次数.这样我们就不必修改每个鸭子发出声音的方法 */ public class QuackCounter implements Quackable { private Quackable quack; private static AtomicInteger atomicInteger = new AtomicInteger(0); public QuackCounter(Quackable quack) { this.quack = quack; } @Override public void quack() { quack.quack(); atomicInteger.incrementAndGet(); } public static AtomicInteger getQuacks() { return atomicInteger; } @Override public void registerObserver(QuackObserver observer) { quack.registerObserver(observer); } @Override public void removeObserver(QuackObserver observer) { quack.removeObserver(observer); } @Override public void notifyObservers() { quack.notifyObservers(); } }
以及之前定义的鸭群,假如我们也想观察鸭群的叫通知,所以鸭群也要变成被观察者,同时将调用委托给
ObservableDelegate
public class Flock implements Quackable { private List<Quackable> quackers = new ArrayList<>(); private QuackObservable observableDelegate; public void add(Quackable quackable) { quackers.add(quackable); observableDelegate = new ObservableDelegate(this); } @Override public void quack() { for (Quackable quackable : quackers) { quackable.quack(); } notifyObservers(); } @Override public void registerObserver(QuackObserver observer) { quackers.forEach(item -> item.registerObserver(observer)); observableDelegate.registerObserver(observer); } @Override public void removeObserver(QuackObserver observer) { quackers.forEach(item -> item.removeObserver(observer)); observableDelegate.removeObserver(observer); } @Override public void notifyObservers() { observableDelegate.notifyObservers(); } }
观察者-QuackObserver
最后、我们要准备观察了,现在需要一些观察者。首先就从定义QuackObserver
接口开始,它只有一个方法update(QuackObservable duck)
,传入的就是呱呱叫对象。
/** * 鸭子观察者,当被通知的时候执行update */ public interface QuackObserver { /** * @param duck 正在呱呱叫的对象 */ void update(QuackObservable duck); }
现在我们来实现一个观察者观察呱呱叫,当收到通知的时候我们就打印下是谁叫的。
/** * 呱呱叫观察者,观察感兴趣的鸭子 */ public class Quackologist implements QuackObserver { @Override public void update(QuackObservable duck) { System.out.println("Quackologist: " duck " just quacked。"); } }
屏幕模拟器
大功告成,我们只要在模拟器上创建观察者,并把观察者注册到感兴趣的被观察者中就实现了。
public class DuckSimulation { public static void main(String[] args) { DuckSimulation simulation = new DuckSimulation(); AbstractDuckFactory duckCountFactory = new DuckCountFactory(new DuckFactory()); simulation.simulate(duckCountFactory); } /** * 模拟屏幕展示功能 */ private void simulate(AbstractDuckFactory duckFactory) { Quackable redheadDuck = duckFactory.createRedheadDuck(); Quackable mallarDuck = duckFactory.createMallarDuck(); Quackable rubberDuck = duckFactory.createRubberDuck(); Quackable duckCall = duckFactory.createDuckCall(); //不想把天鹅的叫声统计,所以不用装饰器装饰 GooseAdapter gooseAdapter = new GooseAdapter(new Goose()); System.out.println("----使用装饰器模式后,统计叫的次数,不包含天鹅,同时使用了工厂模式产生鸭子---"); System.out.println("--使用组合模式管理鸭子群--"); // 主要鸭子群 Flock flockOfDucks = new Flock(); flockOfDucks.add(redheadDuck); flockOfDucks.add(mallarDuck); flockOfDucks.add(rubberDuck); flockOfDucks.add(duckCall); flockOfDucks.add(gooseAdapter); // 绿头鸭群 Flock mallarFlock = new Flock(); Quackable mallarDuck1 = duckFactory.createMallarDuck(); Quackable mallarDuck2 = duckFactory.createMallarDuck(); Quackable mallarDuck3 = duckFactory.createMallarDuck(); Quackable mallarDuck4 = duckFactory.createMallarDuck(); mallarFlock.add(mallarDuck1); mallarFlock.add(mallarDuck2); mallarFlock.add(mallarDuck3); mallarFlock.add(mallarDuck4); //观察者模式观察指定鸭子的叫,flockOfDucks 被观察者注册了 一个观察者 System.out.println("====观察者模式 start==="); flockOfDucks.registerObserver(new Quackologist()); System.out.println("----主要鸭子群模拟器----"); simulate(flockOfDucks); System.out.println("---绿头鸭群模拟---"); simulate(mallarFlock); System.out.println("一共叫了 " QuackCounter.getQuacks() " 次"); } private void simulate(Quackable quackable) { quackable.quack(); } }
主要的改动就是
flockOfDucks.registerObserver(new Quackologist());
创建观察者,并且观察 flockOfDucks
这个鸭群的呱呱叫情况。
总结
最后我们用这个例子主要是打开读者的思维,遇到不同场景根据设计模式的特性便可以将问题迎刃而解,而且代码也变得优雅、高内聚低耦合,代码也得到了复用。
从一开始的鹅适配-「适配器模式」,再到为鸭子叫计数功能增强使用「装饰器模式」,接着创建很多鸭子。就想到了创建型模式「工厂模式」,为了管理鸭群-则想到了「组合模式」,最后想要观察特定鸭子叫,便很快的使用了观察者模式、同时还运用了「委托模式」使得注册观察的代码复用。
若对使用到的设计模式不熟悉可以阅读历史文章了解,欢迎大家提出意见以及指正。