复合模式又叫模式的模式,就是说多个模式可以一个场景下携手合作,成为更强大的存在。本章将重访鸭子模拟器,来学习如何在一个解决方案中结合两个或多个模式,以解决一般或者重复发生的问题。下面我们将与鸭子重聚,从头重建鸭子模拟器,并通过使用一堆模式来赋予它一些有趣的能力。开工……
重现简单的鸭子模拟器场景
创建一个Quackable接口,所有鸭子都实现该接口
123public interface Quackable {public void quack();}创建鸭子类
123456public class MallardDuck implements Quackable {public void quack() {System.out.println("Quack");}}123456public class RedheadDuck implements Quackable {public void quack() {System.out.println("Quack");}}再假如几个不是真正的鸭子(鸭鸣器、橡皮鸭)
123456public class DuckCall implements Quackable {public void quack() {System.out.println("Kwak");}}123456public class RubberDuck implements Quackable {public void quack() {System.out.println("Squeak");}}测试
12345678910111213141516171819public 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
接口来完成。下面会在这基础上逐步添加需求,同时我们会将之前学过的模式运用在其中。
有鸭子的地方可能也有鹅
只要有水塘的地方,就大概会有鸭子和鹅,如何让这个模拟器支持鹅叫呢。这个地方用到了适配器模式:
创建鹅,并且鹅的叫声不是Quack,而是Honk
12345public class Goose {public void honk() {System.out.println("Honk");}}模拟器期望看到的是
Quackable
接口,那么我们创建适配器,将鹅适配成鸭子123456789101112public class GooseAdapter implements Quackable {Goose goose;public GooseAdapter(Goose goose) {this.goose = goose;}public void quack() {goose.honk();}}测试,在模拟器中使用鹅
123456789101112131415161718192021public 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();}}
统计呱呱叫的次数
这个需求是要在不改变鸭子类的情况下,统计呱呱叫的次数,书上用的是装饰者模式,我觉得代理模式也可以实现,我们一起来看下。
装饰者模式统计呱呱叫
创建装饰者,包装鸭子
123456789101112131415161718public class QuarckCounter implements Quackable {Quackable duck;static int numberOfQuacks;public QuarckCounter(Quackable duck) {this.duck = duck;}public void quack() {duck.quack();numberOfQuacks++;}public static int getNumberOfQuacks() {return numberOfQuacks;}}测试,但不计入鹅叫
123456789101112131415161718192021222324public 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();}}
代理模式统计呱呱叫
创建代理类,其中
Test.numberOfQuacks
在下面的测试类中123456789101112131415public class QuackCounterHandler implements InvocationHandler {Quackable duck;public QuackCounterHandler(Quackable duck) {this.duck = duck;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equals("quack")) {Test.numberOfQuacks++;}return method.invoke(duck, args);}}测试,但不计入鹅叫
12345678910111213141516171819202122232425262728293031323334public 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();}}
两种方式都实现了在不改变鸭子类的情况下,统计了呱呱叫的次数。当然,代理模式是额外想出来的,下面还是假定我们使用的还是装饰者模式的版本。继续往下加需求。
优质地装饰鸭子
上面我们用装饰者模式来统计叫声的时候,没有把鹅叫算进去。也就是说装饰者模式下有包装才有效果,没包装就没有效果。为何不把需要包装的对象集中在一个地方创建呢?我们需要一些质量控制来确保鸭子一定是被包装起来的。需要建造一个工厂,创建装饰过的鸭子。此工厂应该生产各种不同类型的鸭子的产品家族,所以要用抽象工厂模式。
创建抽象工厂
123456789public abstract class AbstractDuckFactory {public abstract Quackable createMallardDuck();public abstract Quackable createRedheadDuck();public abstract Quackable createDuckCall();public abstract Quackable createRubberDuck();}先创建一个没有装饰过的正常鸭子工厂,该类虽然没用,但是可以作为抽象工厂模式完整性的展示
123456789101112131415161718192021public class DuckFactory extends AbstractDuckFactory {public Quackable createMallardDuck() {return new MallardDuck();}public Quackable createRedheadDuck() {return new RedheadDuck();}public Quackable createDuckCall() {return new DuckCall();}public Quackable createRubberDuck() {return new RubberDuck();}}创建我们真正需要的工厂,装饰过的鸭子工厂
123456789101112131415161718192021public class CountingDuckFactory extends AbstractDuckFactory {public Quackable createMallardDuck() {return new QuarckCounter(new MallardDuck());}public Quackable createRedheadDuck() {return new QuarckCounter(new RedheadDuck());}public Quackable createDuckCall() {return new QuarckCounter(new DuckCall());}public Quackable createRubberDuck() {return new QuarckCounter(new RubberDuck());}}测试
123456789101112131415161718192021222324public 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();}}
管理一群鸭子
现在管理者想把鸭子视为一个集合,甚至是子集合来管理,目的可能是为了下一次命令,就能让整个集合的鸭子听从命令。还记得,组合模式允许我们像对待单个对象一样对待对象集合,下面来看看如何使用。
构建一群会鸭子,确切地说应该是一群Quackable
12345678910111213141516public class Flock implements Quackable {ArrayList<Quackable> quackers = new ArrayList<>();public void add(Quackable quacker) {quackers.add(quacker);}public void quack() {Iterator<Quackable> iterator = quackers.iterator();while (iterator.hasNext()) {Quackable quackable = iterator.next();quackable.quack();}}}测试
1234567891011121314151617181920212223242526272829303132333435363738public 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();}}
这个模式可以随意组合鸭子们,在某些场景下简单有效。
追踪个别鸭子
现在又出了一个需求,我们要追踪个别的鸭子,要有办法持续追踪它的实时呱呱叫,观察者模式正好派上用场了。
定义观察者接口,只有一个方法,就是更新被观察的对象
123public interface Observer {public void update(QuackObservable duck);}定义被观察者,用于注册和通知观察者。这个接口和上面的观察者可谓是你中有我,我中有你。
12345public interface QuackObservable {public void registerObserver(Observer observer);public void notifyObservers();}Quackable接口继承被观察者接口
123public interface Quackable extends QuackObservable {public void quack();}创建被观察者辅助类,代理所有被观察者的注册和通知观察者的请求
12345678910111213141516171819202122232425262728293031323334353637public class Observable implements QuackObservable {/*** 用于存放观察者*/ArrayList<Observer> observers = new ArrayList<>();/*** 被观察者*/QuackObservable duck;public Observable(QuackObservable duck) {this.duck = duck;}/*** 注册观察者** @param observer*/public void registerObserver(Observer observer) {observers.add(observer);}/*** 通知观察者有变更*/public void notifyObservers() {Iterator<Observer> iterator = observers.iterator();while (iterator.hasNext()) {Observer observer = iterator.next();observer.update(duck);}}}将被观察者辅助类组合到被观察者对象中
1234567891011121314151617181920212223242526272829303132333435public class MallardDuck implements Quackable {/*** 组合被观察者辅助类*/Observable observable;public MallardDuck() {// 将被观察者本人传递给辅助类this.observable = new Observable(this);}public void quack() {System.out.println("Quack");notifyObservers();}/*** 将注册观察者委托给辅助类** @param observer*/public void registerObserver(Observer observer) {observable.registerObserver(observer);}/*** 将通知观察者委托给辅助类*/public void notifyObservers() {observable.notifyObservers();}}实现监听呱呱叫的观察者
1234567891011public class Quackologist implements Observer {/*** 接收到呱呱叫通知** @param duck*/public void update(QuackObservable duck) {System.out.println("Quackologist:" + duck + " just quack.");}}测试
12345678910111213public 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模式。