软件开发的不变真理
CHANGE(改变)
不管当初软件设计得多好,一段时间之后,总是需要成长和改变,否则软件就会“死亡”
类之间的关系
- IS-A(是一个)
- HAS-A(有一个)
- IMPLEMENTS(实现)
类图
https://www.jianshu.com/p/57620b762160
类图基础属性:
-表示private
#表示protected
_下划线表示static
斜体表示抽象
继承:空心三角形+实线
实现(比如实现接口):空心三角形+虚线
依赖(对于两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务时,这两个对象之间主要体现为依赖关系。):虚线箭头
关联(对于两个相对独立的对象,当一个对象的实例与另一个对象的一些特定实例存在固定的对应关系时,这两个对象之间为关联关系):实线箭头
聚合(体现has-a的关系):空心菱形+实线箭头
组合(contains-a的关系):实心菱形+实线箭头
设计原则
- 封装变化(找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起)
- 针对接口编程,而不是针对实现编程
- 多用组合,少用继承
- 为交互对象之间的松耦合设计而努力
- 类应该对扩展开放,对修改关闭(开闭原则)
- 依赖抽象,不要依赖具体类(依赖倒置原则:不能让高层组件依赖低层组件,而且,不管高层或低层组件,二者都应该依赖于抽象)
- 只和朋友交谈(最少知识原则:要减少对象之间的交互,只留下几个“密友”,不要让太多的类耦合在一起)
- 别找我,我会找你(好莱坞原则,高层组件对待低层组件的方式是:别调用我们,我们会调用你)
- 类应该只有一个改变的理由(单一责任原则,尽量让每个类保持单一责任,一个类应该只有一个引起变化的原因)
设计模式
策略模式
定义(意图)
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
动机(问题以及如何解决)
- 软件构建中,某些对象算法可能多种多样,经常改变,如果将它们都编码到对象中,会使得对象非常复杂,有时支持不适用的算法也会造成性能负担
- 在运行时透明地更改对象的算法,将算法与对象本身解耦,从而避免上述问题
适用性(适用场合)
当存在以下情况时使用策略模式:
- 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法
- 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式
- 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的策略类中以代替这些条件语句
结构(类图)
参与者(涉及的类和对象的责任和角色)
- Strategy
定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法 - ConcreteStrategy
具体策略,以Strategy接口实现某具体算法 - Context
Context表示上下文,它用一个ConcreteStrategy对象来配置,维护一个对Strategy对象的引用,可定义一个接口来让Strategy访问它的数据
协作(参与者如何合作)
- Strategy和Context相互作用以实现选定的算法。当算法被调用时,Context可以将该算法所需要的所有数据都传递给该Strategy。或者,Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context
- Context将它的客户的请求转发给它的Strategy。客户通常创建并传递一个ConcreteStrtegy对象给该Context。这样,客户仅与Context交互。通常有一系列的ContextStrategy类可供客户从中选择
结果(采用模式后可能产生的好与坏结果)
好:
- Strategy类层次为Context定义了一系列的可供重用的算法或行为
- 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
- 消除了一些条件语句。Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句
- Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间/空间权衡取舍要求从不同策略中进行选择
坏:
- 客户必须了解不同的Strategy
- 无论各个ConcreteStrategy实现的算法是简单还是复杂,它们都共享Strategy定义的接口。因此很可能某些ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。若存在这样的问题,那么将需要在Strategy和Context之间更进行紧密的耦合
- 增加了对象的数目
示例代码
场景1
有很多品种的鸭子,鸭子有两个行为:飞和叫,对于每个品种,具体的行为不一样。对于这种场景,可以用策略模式
我们需要定义鸭子为抽象类,具体的鸭子会继承这个抽象类,Duck抽象类中需要定义两个私有成员,表示飞和叫的策略,飞和叫的行为是接口,对应不同的具体实现,这些实现是可随时互相替换的,也就是只要改变具体鸭子类对应的策略即可
Duck抽象类
publicabstractclassDuck { privateFlyBehaviorflyBehavior; privateQuackBehaviorquackBehavior; publicvoidperformFly() { flyBehavior.fly(); } publicvoidperformQuack() { quackBehavior.quack(); } publicabstractvoiddisplay(); publicvoidsetFlyBehavior(FlyBehaviorflyBehavior) { this.flyBehavior=flyBehavior; } publicvoidsetQuackBehavior(QuackBehaviorquackBehavior) { this.quackBehavior=quackBehavior; } publicDuck() { } }
策略接口
publicinterfaceFlyBehavior { publicvoidfly(); }
publicinterfaceQuackBehavior { publicvoidquack(); }
具体的策略实现
publicclassFlyWithWingsimplementsFlyBehavior { publicvoidfly() { System.out.println("I can fly with wings"); } }
publicclassQuackimplementsQuackBehavior { publicvoidquack() { System.out.println("quack...quack"); } }
具体的鸭子实现
publicclassMallardDuckextendsDuck { publicvoiddisplay() { System.out.println("I am a com.my.StrategyPattern.Object.MallardDuck!"); } publicMallardDuck() { this.setFlyBehavior(newFlyWithWings()); this.setQuackBehavior(newQuack()); } }
场景2
有一个游戏,这个游戏中玩家可以扮演很多种不同的角色,不同角色可以装备不同的武器,每种武器的攻击行为都不同,但角色和武器有对应关系,比如国王只能用剑,盗贼只能用小刀,但未来可能会出现新的武器可以使用
需要定义角色为抽象类,该抽象类中包含私有成员武器接口,武器接口可以对应不同的具体实现
角色抽象类
publicabstractclassCharacter { privateWeaponBehaviorweaponBehavior; publicabstractvoidfight(); publicvoidsetWeapon(WeaponBehaviorweaponBehavior) { this.weaponBehavior=weaponBehavior; } publicWeaponBehaviorgetWeaponBehavior() { returnthis.weaponBehavior; } publicCharacter() { } }
武器接口
publicinterfaceWeaponBehavior { publicvoiduseWeapon(); }
具体的角色实现
publicclassKingextendsCharacter { publicvoidfight() { this.getWeaponBehavior().useWeapon(); } publicKing() { this.setWeapon(newSwordBehavior()); } }
具体的武器实现
publicclassSwordBehaviorimplementsWeaponBehavior { publicvoiduseWeapon() { System.out.println("I am a saber!"); } }
相关模式(此模式与其他模式之间的关系)
蝇量模式:Strategy对象通常是很好的轻量级对象
观察者模式
定义
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新
动机
观察者模式中的关键对象是Subject和观察者。一个Subject可以有任意数目的依赖它的观察者。一旦Subject的状态发生改变,所有的观察者都得到通知。
这种交互也称为发布-订阅。Subject是通知的发布者。它发出通知时并不需知道谁是它的观察者。可以有任意数目的观察者订阅并接收通知
适用性
在以下任一情况下可以使用观察者模式:
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用
- 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变
- 当一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,你不希望这些对象是紧耦合的
结构
参与者
- Subject
Subject知道它的观察者。可以有任意多个观察者观察同一个Subject。Subejct提供注册和删除观察者对象的接口 - Observer
为那些在Subject发生改变时需获得通知的对象定义一个更新接口 - ConcreteSubject(具体Subject)
将有关状态存入各ConcreteObserver对象,当它的状态发生改变时,向它的各个观察者发出通知 - ConcreteObserver
维护一个指向ConcreteSubject对象的引用。存储有关状态,这些状态应与目标的状态保持一致。实现Observer的更新接口以使自身状态与目标的状态保持一致
协作
- 当ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者
- 在得到一个具体Subject的改变通知后,ConcreteObserver对象可向目标对象查询信息。ConcreteObserver使用这些信息以使它的状态与目标对象的状态一致
结果
好:
- Subject和观察者之间的抽象耦合。一个Subject所知道的仅仅是它有一系列观察者,每个都符合抽象的Observer类的简单接口。Subject不知道任何一个观察者属于哪一个具体的类。这样Subejct和观察者之间的耦合是抽象的和最小的
- 支持广播通信。不像通常的请求,Subject发送的通知不需指定它的接收者。通知被自动广播给所有已向该Subject对象登记的有关对象。Subject对象并不关心到底有多少对象对自己感兴趣;它唯一的责任就是通知它的各观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。
坏:
- 意外的更新。因为一个观察者并不知道其他观察者的存在,它可能对改变Subject的最终代价一无所知。在Subject上一个看似无害的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外,如果依赖准则的定义或维护不当,常常会引起错误的更新,这种错误通常很难捕获
实现
- 创建Subejct到其观察者之间的映射。一个Subject对象跟踪它应通知的观察者的最简单的方式是显式地在Subject中保存对它们的引用。然而,当Subject很多而观察者较少时,这样存储可能代价太高。一个解决办法是用时间换空间,用一个关联查找机制(例如一个Hash表)来维护Subject到观察者的映射。这样一个没有观察者的Subject就不产生存储开销。但另一方面,这一方法增加了访问观察者的开销
- 观察多个Subject。在某些情况下,一个观察者依赖于多个Subject可能是有意义的。在这种情况下,必须扩展Update接口以使观察者知道是哪一个Subject送来的通知。Subejct对象可以简单地将自己作为Update操作的一个参数,让观察者知道应去检查哪一个Subject
- 谁触发更新?Subject和它的观察者依赖于通知机制来保持一致。但到底哪一个对象调用notify来触发更新?此时有两个选择:
- 由subject对象的状态设定操作在改变subject对象的状态后自动调用notify。这种方法的优点是客户不需要记住要在subject对象上调用notify,缺点是多个连续的操作会产生多次连续的更新,可能效率较低
- 由客户负责在适当的时候调用Notify。这样做的优点是客户可以在一系列的状态改变完成后再一次性地触发更新,避免了不必要的中间更新。缺点是给客户增加了触发更新的责任。
- 对已删除Subject的悬挂引用。删除一个Subject时应注意不要在其观察者中遗留对该Subject的悬挂引用。一种避免悬挂引用的方法是,当一个Subject被删除时,让它通知它的观察者将对该Subject的引用复位。一般来说,不能简单地删除观察者,因为其他的对象可能会引用它们,或者也可能它们还在观察其他的Subejct
- 在发出通知前确保目标的状态自身是一致的
- 显式地指定感兴趣的改变。可以扩展Subject的注册接口,让各观察者注册为仅对特定事件感兴趣,以提高更新的效率。当一个事件发生时,Subject仅通知那些已注册为对该事件感兴趣的观察者
- 封装复杂的更新语义。当Subject和观察者间的依赖关系特别复杂时,可能需要一个维护这些关系的对象。我们称这样的对象为更改管理器(ChangeManager)。它的目的是尽量减少观察者反映其Subject的状态变化所需的工作量。例如,如果一个操作涉及到对几个相互依赖的Subject进行改动,就必须保证仅有在所有的Subject都更改完毕后,才一次性地通知他们的观察者,而不是每个Subject都通知观察者ChangeManager有3个责任:
- 它将一个Subejct映射到它的观察者并提供一个接口来维护这个映射。这就不需要由Subejct来维护对其观察者的引用,反之亦然
- 它定义一个特定的更新策略
- 根据一个Subject的请求,它更新所有依赖于这个Subject的观察者
已知应用
- MVC
- jdk中内置了观察者模式
- Swing
- rmi
相关模式
桥接模式:通过封装复杂的更新语义,ChangeManager充当目标和观察者之间的中介者
单例模式:ChangeManager可使用单例模式来保证它是唯一的并且是可全局访问的
示例代码
场景
有设备一直在监测天气信息,比如温度和湿度,显示器需要监听天气信息的改变,如果天气信息改变,需要改变自身状态并显示最新的天气
需要定义天气信息为被观察者,显示器为观察者
JDK实现
JDK自带了观察者模式
publicclassWeatherDataextendsObservable { privatefloattemperature; privatefloathumidtity; privatefloatpressure; publicfloatgetTemperature() { returntemperature; } publicfloatgetHumidtity() { returnhumidtity; } publicfloatgetPressure() { returnpressure; } publicvoidsetMessurements(floattemperature, floathumidtity, floatpressure) { this.temperature=temperature; this.humidtity=humidtity; this.pressure=pressure; messurementsChanged(); } publicvoidmessurementsChanged() { this.setChanged(); this.notifyObservers(); } }
publicinterfaceDisplayElement { publicvoiddisplay(); }
publicclassCurrentConditionsDisplayimplementsDisplayElement, Observer { privatefloattemperature; privatefloathumidity; privatefloatpressure; privateObservableobservable; publicvoiddisplay() { System.out.println(StringUtils.join(newString[]{"current condition : temperature is ", String.valueOf(temperature), "; humidity is ", String.valueOf(humidity)})); } publicCurrentConditionsDisplay(Observableobservable) { this.observable=observable; observable.addObserver(this); } publicvoidupdate(Observableo, Objectarg) { if (oinstanceofWeatherData) { WeatherDataweatherData= (WeatherData) o; this.temperature=weatherData.getTemperature(); this.humidity=weatherData.getHumidtity(); this.pressure=weatherData.getPressure(); display(); } } }
自己实现观察者模式
需要定义主题接口,该接口需要定义3个方法:注册、移除、通知观察者
天气信息需要实现主题接口,并在内部维护观察者的集合
需要定义观察者接口,该接口需要定义update方法,用于改变观察者的状态
显示器需要实现观察者接口,并在内部维护它所订阅的主题
publicinterfaceSubject { publicvoidregistObserver(Observerobserver); publicvoidremoveObserver(Observerobserver); publicvoidnotifyObservers(); }
publicclassWeatherDataimplementsSubject { privateList<Observer>observerList; privatefloattemperature; privatefloathumidtity; privatefloatpressure; publicfloatgetTemperature() { returntemperature; } publicfloatgetHumidtity() { returnhumidtity; } publicfloatgetPressure() { returnpressure; } publicWeatherData() { observerList=newArrayList<>(); } publicvoidregistObserver(Observerobserver) { observerList.add(observer); } publicvoidremoveObserver(Observerobserver) { observerList.remove(observer); } publicvoidnotifyObservers() { for (Observerobserver : observerList) { observer.update(temperature, humidtity, pressure); } } publicvoidsetMessurements(floattemperature, floathumidtity, floatpressure) { this.temperature=temperature; this.humidtity=humidtity; this.pressure=pressure; messurementsChanged(); } publicvoidmessurementsChanged() { notifyObservers(); } }
publicinterfaceObserver { publicvoidupdate(floattemperature, floathumidity, floatpressure); }
publicinterfaceDisplayElement { publicvoiddisplay(); }
publicclassCurrentConditionsDisplayimplementsDisplayElement, Observer { privateSubjectweatherData; privatefloattemperature; privatefloathumidity; privatefloatpressure; publicCurrentConditionsDisplay(SubjectweatherData) { this.weatherData=weatherData; weatherData.registObserver(this); } publicvoidupdate(floattemperature, floathumidity, floatpressure) { this.temperature=temperature; this.humidity=humidity; this.pressure=pressure; display(); } publicvoiddisplay() { System.out.println(StringUtils.join(newString[]{"current condition : temperature is ", String.valueOf(temperature), "; humidity is ", String.valueOf(humidity)})); } }
装饰者模式
定义
装饰者模式动态地将责任附加到对象上。若要拓展功能,装饰者提供了比继承更有弹性的替代方案
动机
希望给某个对象而不是整个类添加一些功能
适用性
以下情况使用Decorator模式:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
- 处理那些可以撤销的职责
- 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类
结构
参与者
- Component
定义一个对象接口,可以给这些对象动态地添加职责 - ConcreteComponent
定义一个对象,可以给这个对象添加一些职责 - Decorator
维持一个指向Component对象的引用,并定义一个与Component接口一致的接口 - ConcreteDecorator
向组件添加职责
协作
Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作
结果
好:
- 比继承更灵活。与对象的继承相比,装饰者模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责
- 避免在层次结构高层的类有太多的特征。Decorator模式提供了一种“即用即付”的方式来添加职责。你可以定义一个简单的类,并且用Decorator类来给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。
坏:
- 产生许多小对象
实现
- 接口的一致性。装饰者对象的接口必须和它所装饰的Component的接口是一致的
已知应用
java.io包
相关模式
- 适配器模式:装饰者模式不同于适配器模式,因为装饰者仅改变对象的职责而不改变它的接口;而适配器将给对象一个全新的接口
- 组合模式:可以将装饰者视为一个退化的、仅有一个组件的组合。然而,装饰者仅给对象添加一些额外的职责,它的目的不在于对象聚集
- 策略模式:装饰者可以改变对象的外表;而策略模式使得你可以改变对象的内核,这是改变对象的两种途径
示例代码
场景1
需要读取一个文本文件,将每个字符转成小写输出,一种方法是创建一个类继承并重写FilterInputStream,Java的io包使用的是装饰者模式,FilterInputStream内部包含InputStream,实际上进行io还是调用InputStream进行io,但是可以进行特殊处理
publicclassLowerCaseInputStreamextendsFilterInputStream { publicLowerCaseInputStream(InputStreamin) { super(in); } publicintread() throwsIOException { intc=super.read(); return (c==-1?c : Character.toLowerCase((char)c)); } publicintread(byte[] b, intoff, intlen) throwsIOException { intresult=super.read(b, off, len); for (inti=off; i<off+result; i++) { b[i] = (byte)Character.toLowerCase((char)b[i]); } returnresult; } }
publicclassInputTest { publicstaticvoidmain(String[] args) throwsIOException { intc; try { InputStreamin=newLowerCaseInputStream(newBufferedInputStream(newFileInputStream("D:\\cs_file\\java\\DesignPattern\\test.txt"))); while ((c=in.read()) >=0) { System.out.print((char)c); } in.close(); } catch (IOExceptione) { e.printStackTrace(); } } }
场景2
奶茶店有很多不同品种的饮料,每种饮料有大中小三种规格,并且饮料都可以加料,比如加大豆等等,并且可以加多份,最后需要按照规格和加的料来算最后的价格为多少
需要定义饮料抽象类,该抽象类有cost抽象方法,用于计算价格。需要定义装饰者抽象类,装饰者抽象类继承了饮料抽象类,之后定义具体的饮料类继承自抽象类,定义具体的装饰者类,表示加料,装饰者类内部包含饮料类,装饰者类在调用cost方法时,会去调用内部包含饮料类的cost方法,并且加上料的钱返回
这样装饰者类可以一直装饰下去,不管用户的饮料加了多少种料,加了多少次,都可以无限套娃下去,最后调用cost方法算出的就是最终的价格
饮料抽象类
publicabstractclassBeverage { Stringdescription="Unknown Beverage"; finalintTALL=0; finalintGRANDE=1; finalintVENTI=2; privateintsize; publicStringgetDescription() { returndescription; } publicintgetSize() { returnthis.size; } publicvoidsetSize(intsize) { this.size=size; } publicabstractdoublecost(); }
装饰者抽象类
publicabstractclassCondimentDecoratorextendsBeverage{ publicabstractStringgetDescription(); publicabstractintgetSize(); }
具体饮料
publicclassHouseBlendextendsBeverage{ publicHouseBlend(intsize) { description=size+" House Blend Coffee"; setSize(size); } publicdoublecost() { return0.89; } }
具体的装饰者(代表加的料)
publicclassSoyextendsCondimentDecorator{ Beveragebeverage; publicSoy(Beveragebeverage) { this.beverage=beverage; } publicintgetSize() { returnbeverage.getSize(); } publicStringgetDescription() { returnbeverage.getDescription() +", Soy"; } publicdoublecost() { intsize=beverage.getSize(); doublecost=beverage.cost(); if (size==beverage.TALL) { cost+=0.10; } elseif (size==beverage.GRANDE) { cost+=0.15; } elseif (size==beverage.VENTI) { cost+=0.20; } returncost; } }
测试代码
publicclassStarbuzzCoffee { publicstaticvoidmain(String[] args) { Beveragebeverage=newEspresso(0); System.out.println(beverage.getDescription() +"$"+beverage.cost()); Beveragebeverage2=newDarkRoast(1); beverage2=newMocha(beverage2); beverage2=newMocha(beverage2); beverage2=newWhip(beverage2); System.out.println(beverage2.getDescription() +"$"+beverage2.cost()); Beveragebeverage3=newHouseBlend(2); beverage3=newSoy(beverage3); beverage3=newMocha(beverage3); beverage3=newWhip(beverage3); System.out.println(beverage3.getDescription() +"$"+beverage3.cost()); } }
工厂方法模式
定义
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类
适用性
在下列情况下可以使用工厂方法模式:
- 当一个类不知道它所必须创建的对象的类的时候
- 当一个类希望由它的子类来指定它所创建的对象的时候
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候
结构
参与者
- Product
定义工厂方法所创建的对象的接口 - ConcreteProduct
实现Product接口 - Creator
- 声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象
- 可以调用工厂方法以创建一个Product对象
- ConcreteCreator
重定义工厂方法以返回一个ConcreteProduct实例
协作
Creator依赖于它的子类来定义工厂方法,返回一个适当的ConcreteProduct实例
结果
好:
- 工厂方法不再将与特定应用有关的类绑定到你的代码中
- 为子类提供钩子。用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活,工厂方法可以给子类一个钩子以提供对象的扩展版本
坏:
- 客户可能仅仅为了创建一个特定的ConcreteProduct对象,就不得不创建Creator的子类
相关模式
抽象工厂模式:抽象工厂模式常常用工厂方法来实现
模板方法模式:工厂方法通常在模板方法模式中被调用
原型模式:原型模式不需要创建Creator的子类,但它们通常要求一个针对Product类的Initialize操作。Creator使用Initialize来初始化对象
抽象工厂模式
定义
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类
适用性
在以下情况下可以使用抽象工厂模式:
- 一个系统要独立于它的产品的创建、组合和表示时
- 一个系统要由多个产品系列中的一个来配置时
- 当你要强调一系列相关的产品对象的设计以便进行联合使用时
- 当你提供一个产品类库,而只想显示它们的接口而不是实现时
结构
参与者
- AbstractFactory
声明一个创建抽象产品对象的操作接口 - ConcreteFactory
实现创建具体产品对象的操作 - AbstractProduct
为一类产品对象声明一个接口 - ConcreteProduct
定义一个将被相应的具体工厂创建的产品对象,实现AbstractProduct接口 - Client
仅使用由AbstractFactory和AbstractProduct类声明的接口
协作
- 通常在运行时刻创建一个ConcreteFactory类的实例。这一具体的工厂创建具有特定实现的产品对象。为创建不同的产品对象,客户应使用不同的具体工厂
- AbstractFactory将产品对象的创建延迟到它的ConcreteFactory子类
结果
好:
- 分离了具体的类。抽象工厂模式帮助你控制一个应用创建的对象的类。因为一个工厂封装创建产品对象的责任和过程,它将客户与类的实现分离。客户通过它们的抽象接口操纵实例。产品的类名也在具体工厂的实现中被分离;它们不出现在客户代码中
- 使得易于交换产品系列。一个具体工厂类在一个应用中仅出现一次——即在它初始化的时候。这使得改变一个应用的具体工厂变得很容易。它只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变。
坏:
- 难以支持新种类的产品。这是因为AbstractFactory接口确定了可以被创建的产品集合。支持新种类的产品就需要扩展该工厂接口
实现
- 将工厂作为单例。一个应用中一般每个产品系列只需要一个ConcreteFactory的实例。因此工厂通常最好实现为一个单例
- 创建产品。AbstractFactory仅声明一个创建产品的接口,真正创建产品是由ConcreteProduct子类实现的。最通常的办法是为每一个产品定义一个工厂方法。一个具体的工厂将为每个产品重定义该工厂方法以生产指定产品。虽然这样的实现很简单,但它却要求每个产品系列都要有一个新的具体工厂子类,即使这些产品系列的差别很小
如果有多个可能的产品系列,具体工厂也可以使用Prototype模式来实现。具体工厂使用产品系列中每一个产品的原型实例来初始化,且它通过复制它的原型来创建新的产品。在基于原型的方法中,使得不是每个新的产品系列都需要一个新的具体工厂类 - 定义可扩展的工厂。AbstractFactory通常为每一种它可以生产的产品定义一个操作。产品的种类被编码在操作型构中。增加一种新的产品要求改变AbstractFactory的接口以及所有与它相关的类。一个更灵活但不太安全的设计是给创建对象的操作增加一个参数。该参数指定了将被创建的对象的种类。
相关模式
工厂方法模式:AbstractFactory类通常用工厂方法实现,但也可用原型模式实现
单例模式:一个具体的工厂通常是一个单例
代码示例
场景
有一个全美连锁的披萨店,用户可以点不同种类的披萨,对于每个分店,对应种类的披萨的制作方式都不同
需要定义一个抽象的工厂类,定义工厂方法用于返回披萨,定义抽象类披萨,具体的披萨继承该类
抽象工厂类
publicabstractclassPizzaStore { publicPizzaorderPizza(Stringtype) { Pizzapizza; pizza=createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); returnpizza; } abstractPizzacreatePizza(Stringtype); }
产品抽象类
publicabstractclassPizza { Stringname; Stringdough; Stringsauce; ArrayListtoppings=newArrayList(); voidprepare() { System.out.println("Preparing "+name); System.out.println("Tossing dough..."); System.out.println("Adding sauce..."); System.out.println("Adding toppings: "); for (inti=0; i<toppings.size(); i++) { System.out.println(" "+toppings.get(i)); } } voidbake() { System.out.println("Bake for 25 minutes at 350"); } voidcut() { System.out.println("Cutting the pizza into diagonal slices"); } voidbox() { System.out.println("Place pizza in official PizzaStore box"); } publicStringgetName() { returnname; } }
具体工厂
publicclassNYPizzaStoreextendsPizzaStore{ PizzacreatePizza(Stringtype) { if ("cheese".equals(type)) { returnnewNYStyleCheesePizza(); } elseif ("veggie".equals(type)) { returnnewNYStyleVeggiePizza(); } elseif ("clam".equals(type)) { returnnewNYStyleClamPizza(); } elseif ("pepperoni".equals(type)) { returnnewNYStylePepperoniPizza(); } else { returnnull; } } }
具体产品
publicclassNYStyleCheesePizzaextendsPizza{ publicNYStyleCheesePizza() { name="NY Style Sauce and Cheese Pizza"; dough="Thin Crust Dough"; sauce="Marinara Sauce"; toppings.add("Grated Reggiano Cheese"); } }
单例模式
定义
单例模式确保一个类只有一个实例,并提供一个全局访问点
动机
对一些类来说,只有一个实例是很重要的。如何才能保证一个类只有一个实例并且这个实例易于被访问?可以让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法
适用性
当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时
结构
参与者
- Singleton
定义一个getInstance方法,允许客户访问它的唯一实例。getInstance是一个类方法
Singleton可能负责创建它自己的唯一实例
协作
客户只能通过Singleton的getInstance操作访问一个Singleton的实例
结果
好:
- 对唯一实例的受控访问。因为Singleton类封装了它的唯一实例,所以它可以严格地控制客户怎样以及何时访问它
相关模式
很多模式可以使用单例模式实现,如抽象工厂模式,Builder构造器模式,原型模式
代码示例
成员变量单例对象一定是私有,并且static和volatile修饰,获取实例方法需要双重判断,第一次判断单例对象是否被创建,如果没有创建,则需要锁住类,并且再判断一次单例对象是否被创建,第一次判断是为了提高效率,如果已经创建好了,就可以直接返回,第二次判断是为了安全,因为另一个线程可能已经创建好了单例对象并释放锁,如果不就行判断,会破坏单例
publicclassChocolateBoiler { privatebooleanempty; privatebooleanboiled; privatestaticvolatileChocolateBoilerchocolateBoiler; privateChocolateBoiler() { empty=true; boiled=false; } publicstaticChocolateBoilergetInstance() { if (chocolateBoiler==null) { synchronized (ChocolateBoiler.class) { if (chocolateBoiler==null) { chocolateBoiler=newChocolateBoiler(); } } } returnchocolateBoiler; } }
命令模式
定义
命令模式将“请求”封装成对象,以便使用不同的请求、队列或日志来参数化其他对象。命令模式也支持可撤销的操作
动机
有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息。命令模式可以将请求本身变成一个对象,这个对象可被存储并像其他的对象一样被传递。
适用性
当你有如下需求时,可使用命令模式:
- 在不同的时刻指定、排列和执行请求
- 支持取消操作。Command的Excute操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”
- 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们
结构
参与者
- Command
声明执行操作的接口 - ConcreteCommand
将一个接收者对象绑定于一个动作。调用接收者相应的操作,以实现Execute - Client
创建一个具体命令对象并设定它的接收者 - Invoker
要求该命令执行这个请求 - Receiver
知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者
协作
- Client创建一个ConcreteCommand对象并指定它的Receiver对象
- 某Invoker对象存储该ConcreteCommand对象
- 该Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤销的,ConcreteCommand就在执行Execute操作之前存储当前状态以用于取消该命令
- ConcreteCommand对象调用它的Receiver的一些操作以执行该请求
结果
好:
- Command模式将调用操作的对象与知道如何实现该操作的对象解耦
- 可将多个命令装配为一个复合命令。复合命令是组合模式的一个实例
- 增加新的Command很容易,因为这无需改变已有的类
实现
- 支持取消和重做。若Command提供Unexecute或Undo操作,就可支持取消和重做功能。为达到这个目的,ConcreteCommand类可能需要存储额外的状态信息。这个状态包括:
- 接收者对象,它真正执行处理该请求的各操作
- 接收者上执行操作的参数
- 若处理请求的操作会改变接收者对象中的某些值,那么这些值也必须先存储起来。接收者还必须提供一些操作,以使该命令可将接收者恢复到它先前的状态
- 若应用只支持一次取消操作,那么只需存储最近一次被执行的命令。而若要支持多级的取消和重做,就需要有一个已被执行命令的历史表列。该表列的最大长度决定了取消和重做的级数。历史表列存储了已被执行的命令序列。向后遍历该表列并逆向执行命令是取消它们的结果;向前遍历并执行命令是重执行它们
已知应用
日程安排、线程池、队列请求(工作队列等)
日志请求
相关模式
组合模式:可被用来实现宏命令
备忘录模式:可用来保持某个状态,命令用这一状态来取消它的效果
代码示例
场景
有一个智能家居遥控器,这个遥控器可以通过按不同按钮操作很多家具,并且可以撤回上一个操作指令,遥控器还有模式按钮,例如party模式,可以同时操作多个家具,也就是宏指令
需要定义命令接口,声明执行和撤回方法,具体的命令和宏命令都实现该接口,对于具体的命令类,内部包含它所控制的家具对象,对于宏命令,内部包含一个命令数组,执行或撤回宏命令即逐个执行或撤回数组中的命令
对于用户,即遥控器而言,需要通过容器维护一组命令,并且保存上一个指令,用于撤销操作
命令接口
命令接口定义执行和撤回方法
publicinterfaceCommand { publicvoidexecute(); publicvoidundo(); }
具体设备
publicclassCeilingFan { publicstaticfinalintHIGH=3; publicstaticfinalintMEDIUM=2; publicstaticfinalintLOW=1; publicstaticfinalintOFF=0; Stringlocation; intspeed; publicCeilingFan(Stringlocation) { this.location=location; speed=OFF; } publicvoidhigh() { speed=HIGH; System.out.println(location+" ceiling fan is on high"); } publicvoidmedium() { speed=MEDIUM; System.out.println(location+" ceiling fan is on medium"); } publicvoidlow() { speed=LOW; System.out.println(location+" ceiling fan is on low"); } publicvoidoff() { speed=OFF; System.out.println(location+" ceiling fan is off"); } publicintgetSpeed() { returnspeed; } publicStringgetLocation() { returnlocation; } }
publicclassLight { privateStringlocation; publicLight(Stringlocation) { this.location=location; } publicvoidon() { System.out.println(location+" light is on"); } publicvoidoff() { System.out.println(location+" light is off"); } }
具体命令
publicclassCeilingFanHighCommandimplementsCommand { CeilingFanceilingFan; intprevSpeed; publicCeilingFanHighCommand(CeilingFanceilingFan) { this.ceilingFan=ceilingFan; } publicvoidexecute() { prevSpeed=ceilingFan.getSpeed(); ceilingFan.high(); } publicvoidundo() { if (prevSpeed==CeilingFan.HIGH) { ceilingFan.high(); } elseif (prevSpeed==CeilingFan.MEDIUM) { ceilingFan.medium(); } elseif (prevSpeed==CeilingFan.LOW) { ceilingFan.low(); } elseif (prevSpeed==CeilingFan.OFF) { ceilingFan.off(); } } }
publicclassLightOnCommandimplementsCommand { Lightlight; publicLightOnCommand(Lightlight) { this.light=light; } publicvoidexecute() { light.on(); } publicvoidundo() { light.off(); } }
宏命令
publicclassMacroCommandimplementsCommand { Command[] commands; publicMacroCommand(Command[] commands) { this.commands=commands; } publicvoidexecute() { for (inti=0; i<commands.length; i++) { commands[i].execute(); } } publicvoidundo() { for (inti=0; i<commands.length; i++) { commands[i].undo(); } } }
操作器
publicclassRemoteControlWithUndo { Command[] onCommands; Command[] offCommands; CommandundoCommand; publicRemoteControlWithUndo() { onCommands=newCommand[7]; offCommands=newCommand[7]; CommandnoCommand=newNoCommand(); for (inti=0; i<7; i++) { onCommands[i] =noCommand; offCommands[i] =noCommand; } undoCommand=noCommand; } publicvoidsetCommand(intslot, CommandonCommand, CommandoffCommand) { onCommands[slot] =onCommand; offCommands[slot] =offCommand; } publicvoidonButtonWasPushed(intslot) { onCommands[slot].execute(); undoCommand=onCommands[slot]; } publicvoidoffButtonWasPushed(intslot) { offCommands[slot].execute(); undoCommand=offCommands[slot]; } publicvoidundoButtonWasPushed() { undoCommand.undo(); } publicStringtoString() { StringBufferstringBuffer=newStringBuffer(); stringBuffer.append("\n------ Remote Control ------\n"); for (inti=0; i<onCommands.length; i++) { stringBuffer.append("[slot "+i+"] "+onCommands[i].getClass().getName() +" "+offCommands[i].getClass().getName() +'\n'); } stringBuffer.append("[undo] "+undoCommand.getClass().getName()); returnstringBuffer.toString(); } }
测试代码
publicclassRemoteLoader { publicstaticvoidmain(String[] args) { RemoteControlWithUndoremoteControl=newRemoteControlWithUndo(); Lightlight=newLight("Living Room"); TVtv=newTV("Living Room"); Stereostereo=newStereo("Living Room"); Hottubhottub=newHottub(); LightOnCommandlightOn=newLightOnCommand(light); StereoOnCommandstereoOn=newStereoOnCommand(stereo); TVOnCommandtvOn=newTVOnCommand(tv); HottubOnCommandhottubOn=newHottubOnCommand(hottub); LightOffCommandlightOff=newLightOffCommand(light); StereoOffCommandstereoOff=newStereoOffCommand(stereo); TVOffCommandtvOff=newTVOffCommand(tv); HottubOffCommandhottubOff=newHottubOffCommand(hottub); Command[] partyOn= {lightOn, stereoOn, tvOn, hottubOn}; Command[] partyOff= {lightOff, stereoOff, tvOff, hottubOff}; MacroCommandpartyOnMacro=newMacroCommand(partyOn); MacroCommandpartyOffMacro=newMacroCommand(partyOff); remoteControl.setCommand(0, partyOnMacro, partyOffMacro); System.out.println(remoteControl); System.out.println("--- Pushing Macro On ---"); remoteControl.onButtonWasPushed(0); System.out.println("--- Pushing Macro Off ---"); remoteControl.offButtonWasPushed(0); } }
适配器模式
定义
适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间
动机
一个应用可能会有一些类具有不同的接口并且这些接口互不兼容,这时我们可以使用适配器模式
适用性
以下情况使用适配器模式:
- 你想使用一个已经存在的类,而它的接口不符合你的需求
- 你想使用一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作
结构
对象适配器依赖于对象组合:
参与者
- Target
定义Client使用的与特定领域相关的接口 - Client
与符合Target接口的对象协同 - Adaptee
定义一个已经存在的接口,这个接口需要适配 - Adapter
对Adaptee的接口与Target接口进行适配
协作
Client在Adapter实例上调用一些操作。接着适配器调用Adaptee的操作实现这个请求
结果
好:
- 允许一个Adapter与多个Adaptee(即Adaptee本身以及它的所有子类)同时工作。Adapter也可以一次给所有的Adaptee添加功能
坏:
- 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身
已知应用
Java中可以将枚举适配到迭代器
相关模式
桥接模式:桥接模式的结构与对象适配器类似,但是桥接模式的出发点不同:桥接模式的目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立地加以改变。而适配器模式则意味着改变一个已有对象的接口
装饰者模式:装饰者模式增强了其他对象的功能而同时又不改变它的接口。因此装饰者对应用程序的透明性比适配器要好。结果是装饰者decorator支持递归组合,而纯粹使用适配器
是不可能实现这一点的
代理模式:代理模式在不改变它的接口的条件下,为另一个对象定义了一个代理
代码示例
场景1
有不同种类的鸭子和火鸡,鸭子和火鸡的叫声不同,现在要让鸭子学火鸡叫,火鸡学鸭子叫,但因为物种不同,它们最终发出的声音还是不同的
需要定义鸭子和火鸡接口,声明叫方法,具体的鸭子和火鸡实现接口,需要定义鸭子和火鸡的适配器类,需要定义鸭子和火鸡适配器类,鸭子适配器类是为了把鸭子伪装成火鸡,所以它需要实现火鸡接口,成员变量是鸭子对象,在调用火鸡接口方法时,实际上还是调用的鸭子方法,火鸡适配器反之
不同接口
publicinterfaceDuck { publicvoidquack(); publicvoidfly(); }
publicinterfaceTurkey { publicvoidgobble(); publicvoidfly(); }
具体类
publicclassWildTurkeyimplementsTurkey { publicvoidgobble() { System.out.println("Gobble gobble"); } publicvoidfly() { System.out.println("I am flying a short distance"); } }
publicclassMallardDuckimplementsDuck { publicvoidquack() { System.out.println("Quack"); } publicvoidfly() { System.out.println("I am flying"); } }
适配器类
publicclassDuckAdapterimplementsTurkey { privateDuckduck; privateRandomrand; publicDuckAdapter(Duckduck) { this.duck=duck; rand=newRandom(); } publicvoidgobble() { duck.quack(); } publicvoidfly() { // 生成一个随机数,该随机数的范围为[0,5)if (rand.nextInt(5) ==0) { duck.fly(); } } }
publicclassTurkeyAdapterimplementsDuck { Turkeyturkey; publicTurkeyAdapter(Turkeyturkey) { this.turkey=turkey; } publicvoidquack() { turkey.gobble(); } publicvoidfly() { for (inti=0; i<5; i++) { turkey.fly(); } } }
测试代码
publicclassDuckTestDrive { publicstaticvoidmain(String[] args) { MallardDuckduck=newMallardDuck(); WildTurkeyturkey=newWildTurkey(); DuckturkeyAdapter=newTurkeyAdapter(turkey); System.out.println("The Turkey says..."); turkey.gobble(); turkey.fly(); System.out.println("\nThe Duck says..."); testDuck(duck); System.out.println("\nThe TurkeyAdapter says..."); testDuck(turkeyAdapter); } staticvoidtestDuck (Duckduck) { duck.quack(); duck.fly(); } }
场景2
自己用迭代器模式实现Emeration和Iterator的互相适配
publicclassEnumerationIteratorimplementsIterator { Enumerationenumeration; publicEnumerationIterator(Enumerationenumeration) { this.enumeration=enumeration; } publicbooleanhasNext() { returnenumeration.hasMoreElements(); } publicObjectnext() { returnenumeration.nextElement(); } }
publicclassIteratorToEnumerationimplementsEnumeration { privateIteratoriterator; publicIteratorToEnumeration(Iteratoriterator) { this.iterator=iterator; } publicbooleanhasMoreElements() { returniterator.hasNext(); } publicObjectnextElement() { returniterator.next(); } }
外观模式(门面模式)
定义
外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用
动机
将一个系统划分成为若干个子系统有利于降低系统的复杂性。一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小。达到该目标的途径之一就是引入一个外观对象,它为子系统中较一般的设施提供了一个单一而简单的界面
适用性
当遇到以下情况时使用外观模式:
- 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以跃过facade层
- 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade将这个子系统与客户已经其他的子系统分离,可以提高子系统的独立性和可移植性
- 当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。若子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系
结构
参与者
- facade
- 知道哪些子系统类负责处理请求
- 将客户的请求代理给适当的子系统对象
- Subsystem classes
- 实现子系统的功能
- 处理由facade对象指派的任务
- 没有facade的任何相关信息
协作
- 客户程序通过发送请求给facade的方式与子系统通讯,facade将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但facade模式本身也必须将它的接口转换成子系统的接口
- 使用facade的客户程序不需要直接访问子系统对象
结果
好:
- 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便
- 它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。facade模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。facade模式可以消除复杂的循环依赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要
实现
- 降低客户-子系统之间的耦合度。用抽象类实现Facade而它的具体子类对应于不同的子系统实现,这可以进一步降低客户与子系统的耦合度。这样,客户就能通过抽象的facade类接口与子系统通讯。这种抽象耦合关系使得客户不知道它使用的是子系统的哪一个实现
相关模式
抽象工厂模式:抽象工厂模式可以与外观模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。
中介者模式:中介者模式与外观模式的相似之处是,它抽象了一些已有的类的功能。然而,中介者的目的是对对象之间的任意通讯进行抽象,通常用于集中不属于任何单个对象的功能。中介者模式中的对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,外观模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道facade的存在
单例模式:通常来说,只需要一个facade对象,因此facade对象通常属于单例模式
代码示例
场景
有一个智能家居设备,这个设备负责家庭影院功能,只要用户按下家庭影院按钮,就会控制各种家具以达到影院模式
需要定义一个门面,该门面维护各类设备作为成员变量,对外提供开启和关闭影院模式的方法,具体方法就是操作各个设备
门面
publicclassHomeTheaterFacade { Amplifieramp; Tunertuner; DvdPlayerdvd; CdPlayercd; Projectorprojector; TheaterLightslights; Screenscreen; PopcornPopperpopper; publicHomeTheaterFacade(Amplifieramp, Tunertuner, DvdPlayerdvd, CdPlayercd, Projectorprojector, TheaterLightslights, Screenscreen, PopcornPopperpopper) { this.amp=amp; this.tuner=tuner; this.dvd=dvd; this.cd=cd; this.projector=projector; this.lights=lights; this.screen=screen; this.popper=popper; } publicvoidwatchMovie(Stringmovie) { System.out.println("Get ready to watch a movie..."); popper.on(); popper.pop(); lights.dim(10); screen.down(); projector.on(); projector.wideScreenMode(); amp.on(); amp.setDvd(dvd); amp.setSurroundSound(); amp.setVolume(5); dvd.on(); dvd.play(movie); } publicvoidendMovie() { System.out.println("Shutting movie theater down..."); popper.off(); lights.on(); screen.up(); projector.off(); amp.off(); dvd.stop(); dvd.eject(); dvd.off(); } }
具体设备
publicclassDvdPlayer { privateStringmovie; publicvoidon() { System.out.println("Top-O-Line DVD Player on"); } publicvoidplay(Stringmovie) { this.movie=movie; System.out.println("Top-O-Line DVD Player playing \""+movie+"\""); } publicvoidstop() { System.out.println("Top-O-Line DVD Player stopped \""+movie+"\""); } publicvoideject() { System.out.println("Top-O-Line DVD Player eject"); } publicvoidoff() { System.out.println("Top-O-Line DVD Player off"); } }
模板方法模式
定义
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
动机
一个模板方法用一些抽象的操作定义一个算法,而子类将重定义这些操作以提供具体的行为。通过使用抽象操作定义一个算法中的一些步骤,模板方法确定了它们的先后顺序,但它允许子类改变这些具体步骤以满足它们各自的需求
适用性
模板方法应用于下列情况:
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码
- 控制子类扩展。模板方法通过只在特定点调用钩子hook操作,这样就只允许在这些点进行扩展
结构
参与者
- AbstractClass(抽象类)
- 定义抽象的原语操作,具体的子类将重定义它们以实现一个算法的各步骤
- 实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义在AbstractClass或其他对象中的操作
- ConcreteClass
实现原语操作以完成算法中与特定子类相关的步骤
协作
ConcreteClass靠AbstractClass来实现算法中不变的步骤
结果
好:
- 模板方法是一种代码复用的基本技术。它们在类库中尤为重要,它们提取了类库中的公共行为
- 模板方法导致一种反向的控制结构,这种结构有时被称为好莱坞法则,即“别找我们,让我们找你”。这指的是一个父类调用一个子类的操作,而不是相反
实现
模板方法调用下列类型的操作:
- 具体的操作(ConcreteClass或对客户类的操作)
- 具体的AbstractClass的操作(即,通常对子类有用的操作)
- 原语操作(即,抽象操作)
- 工厂方法
- 钩子方法:钩子方法提供了缺省的行为,子类可以在必要时进行扩展。一个钩子操作在缺省时通常是空操作
很重要的一点是模板方法应该指明哪些操作是钩子操作(可以被重定义),以及哪些是抽象操作(必须被重定义)
尽量减少原语操作。定义模板方法的一个重要目的是尽量减少一个子类具体实现该算法时必须重定义的那些原语操作的数目。需要重定义的操作越多,客户程序越冗长
已知应用
jdk类库
相关模式
工厂方法模式:工厂方法模式常被模板方法调用
策略模式:模板方法使用继承来改变算法的一部分。策略模式使用委托来改变整个算法
代码示例
场景1
对于饮料的做法,都是烧开水,倒入杯中搅拌,然后再根据用户需求加料,可以选择加料,也可以不加,那么饮料的做法有公共的部分,也有个性化的部分
需要定义饮料抽象类,内部定义默认的制作饮料方法,具体的饮料继承该类,重写方法以实现个性化操作
模版抽象类
publicabstractclassCaffeineBeverageWithHook { voidprepareRecipe() { boilWater(); brew(); pourInCup(); if (customerWantsCondiments()) { addCondiments(); } } abstractvoidbrew(); abstractvoidaddCondiments(); voidboilWater() { System.out.println("Boiling water"); } voidpourInCup() { System.out.println("Pouring into cup"); } booleancustomerWantsCondiments() { returntrue; } }
具体的个性化类
publicclassCoffeeWithHookextendsCaffeineBeverageWithHook { voidbrew() { System.out.println("Dripping Coffee through filter"); } voidaddCondiments() { System.out.println("Adding Sugar and Milk"); } booleancustomerWantsCondiments() { Stringanswer=getUserInput(); if (answer.toLowerCase().startsWith("y")) { returntrue; } else { returnfalse; } } privateStringgetUserInput() { Stringanswer=null; System.out.println("Would you like milk and sugar with your coffee (y/n)? "); BufferedReaderin=newBufferedReader(newInputStreamReader(System.in)); try { answer=in.readLine(); } catch (IOExceptionioe) { System.out.println("IO error trying to read your answer"); } if (answer==null) { return"no"; } returnanswer; } }
场景2
自定义类实现Comparable接口,并重写compareTo接口,用到的就是模版方法模式,因为jdk在排序的时候判断对象的顺序就是按照你重写的方法进行判断的
迭代器模式
定义
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示
动机
一个聚合对象,如列表list,应该提供一种方法让别人可以访问它的元素,而又不需要暴露它的内部结构。此外,针对不同的需要,可能要以不同的方式遍历这个列表。
迭代器模式可以解决上述问题。这一模式的关键思想是将对列表的访问和遍历从列表对象中分离出来并放入一个迭代器对象中。迭代器类定义了一个访问该列表元素的接口。迭代器对象负责跟踪当前的元素,即它知道哪些元素已经遍历过了
将遍历机制和列表对象分离使我们可以定义不同的迭代器来实现不同的遍历策略,而无需在列表接口中列举它们
适用性
迭代器模式可用来:
- 访问一个聚合对象的内容而无需暴露它的内部表示
- 支持对聚合对象的多种遍历
- 为遍历不同的聚合结构提供一个统一的接口
结构
参与者
- Iterator
迭代器定义访问和遍历元素的接口 - ConcreteIterator
具体迭代器实现迭代器接口
对该聚合遍历时跟踪当前位置 - Aggregate
聚合定义创建相应迭代器对象的接口 - ConcreteAggregate
具体聚合实现创建相应迭代器的接口,并能够计算出待遍历的后继对象
协作
ConcreteIterator跟踪聚合中的当前对象,并能够计算出待遍历的后继对象
结果
好:
- 它支持以不同的方式遍历一个集合。复杂的集合可用多种方式进行遍历。迭代器模式使得改变遍历算法变得很容易:仅需用一个不同的迭代器的实例代替原先的实例即可。也可以自定义迭代器的子类以支持新的遍历
- 迭代器简化了集合的接口。有了迭代器的遍历接口,集合本身就不需要类似的遍历接口了。这就简化了集合的接口
- 在同一个集合上可以有多个遍历。每个迭代器保持它自己的遍历状态。因此可以同时进行多个遍历
实现
- 谁控制迭代?
一个基本的问题是决定由哪一方来控制该迭代,是迭代器还是使用该迭代器的客户。当由客户来控制迭代时,该迭代器称为一个外部迭代器,而当由迭代器控制迭代时,该迭代器称为一个内部迭代器。使用外部迭代器的客户必须主动推进遍历的步伐,显式地向迭代器请求下一个元素。相反地,若使用内部迭代器,客户只需向其提交一个待执行的操作,而迭代器将对聚合中的每一个元素实施该操作
外部迭代器比内部迭代器更灵活
- 谁定义遍历算法?
迭代器不是唯一可定义遍历算法的地方。聚合本身也可以定义遍历算法,并在遍历过程中用迭代器来存储当前迭代的状态。我们称这种迭代器为一个游标(cursor),因为它仅用来指示当前位置。客户会以这个游标为一个参数调用该集合的next操作,而next操作将改变这个指示器的状态
如果迭代器负责遍历算法,那么将易于在相同的集合上使用不同的迭代算法,同时也易于在不同的集合上重用相同的算法。从另一方面说,遍历算法可能需要访问集合的私有变量
如果这样,将遍历算法放入迭代器中会破坏集合的封装性
- 迭代器健壮程度如何。在遍历一个集合的同时改变这个集合可能是危险的。若在遍历集合的时候增加或删除该集合元素,可能会导致两次访问同一个元素或漏掉某个元素。一个健壮的迭代器保证插入和删除操作不会干扰遍历,且不需要拷贝该集合。有许多方法来实现健壮的迭代器。其中大多数需要向这个集合注册该迭代器。当插入或删除元素时,该集合要么调整迭代器的内部状态。要么在内部维护额外的信息以保证正确的遍历
- 用于复合对象的迭代器。在组合模式中的那些递归集合结构上,外部迭代器可能难以实现。因为在该结构中不同对象处于嵌套集合的多个不同层次,因此一个外部迭代器为跟踪当前的对象必须存储一条纵贯该集合的路径。有时使用一个内部迭代器会更容易一些。它仅需递归地调用自己即可,这样就隐式地将路径存储在调用栈中,而无须显式地维护当前对象位置
若复合中的节点有一个接口可以从一个节点移到它的兄弟节点、父节点和子节点,那么基于游标的迭代器是更好的选择。游标只需跟踪当前的结点,它可依赖这种结点接口来遍历该复合对象 - 空迭代器。一个空迭代器(NullIterator)是一个退化的迭代器,它有助于处理边界条件。根据定义,一个空迭代器总是已经完成了遍历:即它的IsDone方法总是返回true
空迭代器使得更容易遍历树形结构的集合(如组合对象)。在遍历过程中的每一结点,都可向当前的元素请求遍历其各个子节点的迭代器。该组合元素将返回一个具体的迭代器。但叶节点元素返回NullIterator的一个实例,这就使我们可以用一种统一的方式实现在整个结构上的遍历
已知应用
JDK中的Iterator
相关模式
组合模式:迭代器常被用于像组合这样的递归结构上
工厂方法模式:多态迭代器靠工厂方法模式来实例化适当的迭代器子类
备忘录模式:常和迭代器模式一起使用,迭代器可使用一个备忘录来捕获一个迭代的状态。迭代器在其内部存储memento
示例代码
场景
有一个饭店,饭店有一个服务员,顾客可以向服务员点菜,可以点蛋糕、晚餐、咖啡三大类,每一大类中又可以点很多小类,在顾客点完单后,服务员需要按大类和小类输出顾客的点单
需要定义菜单接口,声明创建迭代器的方法,对于每一大类来说,都属于菜单,都去实现菜单接口,分别有各自的迭代器,它们都可以去实现一个自定义的迭代器接口,迭代器接口继承JDK中的Iterator类,可以根据具体需求添加方法,每个专属迭代器都根据具体的数据结构进行遍历
Aggregate
publicinterfaceMenu { publicIteratorcreateIterator(); }
Iterator
publicinterfaceIteratorextendsjava.util.Iterator { booleanhasNext(); Objectnext(); }
ConcreteAggregate
publicclassPancakeHouseMenuimplementsMenu{ ArrayList<MenuItem>menuItems; publicPancakeHouseMenu() { menuItems=newArrayList<>(); addItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99); addItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99); addItem("Blueberry Pancakes", "Pancakes made with fresh blueberries", true, 3.49); addItem("Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59); } publicvoidaddItem(Stringname, Stringdescription, booleanvegetarian, doubleprice) { MenuItemmenuItem=newMenuItem(name, description, vegetarian, price); menuItems.add(menuItem); } publicIteratorcreateIterator() { returnnewPancakeHouseIterator(menuItems); } }
ConcreteIterator
publicclassPancakeHouseIteratorimplementsIterator { ArrayList<MenuItem>items; intposition=0; publicPancakeHouseIterator(ArrayList<MenuItem>items) { this.items=items; } publicObjectnext() { MenuItemmenuItem=items.get(position); position=position+1; returnmenuItem; } publicbooleanhasNext() { if (position>=items.size()) { returnfalse; } else { returntrue; } } }
需要遍历的类
publicclassMenuItem { Stringname; Stringdescription; booleanvegetarian; doubleprice; publicMenuItem(Stringname, Stringdescription, booleanvegetarian, doubleprice) { this.name=name; this.description=description; this.vegetarian=vegetarian; this.price=price; } publicStringgetName() { returnname; } publicStringgetDescription() { returndescription; } publicbooleanisVegetarian() { returnvegetarian; } publicdoublegetPrice() { returnprice; } }
外部调用者
publicclassWaitress { MenupancakeHouseMenu; MenudinerMenu; MenucafeMenu; publicWaitress(MenupancakeHouseMenu, MenudinerMenu, MenucafeMenu) { this.pancakeHouseMenu=pancakeHouseMenu; this.dinerMenu=dinerMenu; this.cafeMenu=cafeMenu; } publicvoidprintMenu() { IteratorpancakeIterator=pancakeHouseMenu.createIterator(); IteratordinerIterator=dinerMenu.createIterator(); IteratorcafeIterator=cafeMenu.createIterator(); System.out.println("MENU\n----\nBREAKFAST"); printMenu(pancakeIterator); System.out.println("\nLUNCH"); printMenu(dinerIterator); System.out.println("\nDINNER"); printMenu(cafeIterator); } privatevoidprintMenu(Iteratoriterator) { while (iterator.hasNext()) { MenuItemmenuItem= (MenuItem) iterator.next(); System.out.print(menuItem.getName() +", "); System.out.print(menuItem.getPrice() +" -- "); System.out.println(menuItem.getDescription()); } } }
组合模式
定义
组合模式允许你将对象组合成树形结构来表现“整体/部分"层次结构。组合能让客户以一致的方式处理个别对象以及对象组合
动机
适用性
以下情况使用组合模式:
- 你想表示对象的部分-整体层次结构
- 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象
结构
参与者
- Component
- 为组合中的对象声明接口
- 在适当的情况下,实现所有类共有接口的缺省行为
- 声明一个接口用于访问和管理Component的子组件
- (可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它
- Leaf
- 在组合中表示叶节点对象,叶节点没有子节点
- Composite
- 定义有子部件的那些部件的行为
- 存储子部件
- 在Component接口中实现与子部件有关的操作
- Client
- 通过Component接口操纵组合部件的对象
协作
用户使用Component类接口与组合结构中的对象进行交互。若接收者是一个叶节点,则直接处理请求。若接收者是Composite,它通常将请求发送给它的子部件,在转发请求之前与/或之后可能执行一些辅助操作
结果
好:
- 定义了包含基本对象和组合对象的类层次结构。基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去。客户代码中,任何用到基本对象的地方都可以使用组合对象
- 简化客户代码。客户可以一致地使用组合结构和单个对象。通常用户不知道(也不关心)处理的是一个叶节点还是一个组合组件。这就简化了客户代码,因为在定义组合的那些类中不需要写一些充斥着选择语句的函数
- 使得更容易增加新类型的组件
新定义的Composite或Leaf子类自动地与已有的结构和客户代码一起工作,客户程序不需因新的Component类而改变
坏:
- 容易增加新组件也会产生一些问题,那就是很难限制组合中的组件
实现
- 显式的父部件引用
保持从子部件到父部件的引用能简化组合结构的遍历和管理。父部件引用可以简化结构的上移和组件的删除,同时父部件引用也支持责任链模式
通常在Component类中定义父部件引用。Leaf和Composite类可以继承这个引用以及管理这个引用的那些操作
对于父部件引用,必须维护一个不变式,即一个组合的所有子节点以这个组合为父节点,而反之该组合以这些结点为子节点。保证这一点最容易的办法是,仅当在一个组合中增加或删除一个组件时,才改变这个组件的父部件。若能在Composite类的Add和Remove操作中实现这种方法,那么所有的子类都可以继承这一方法,并且将自动维护这一不变式 - 使用高速缓冲存储改善性能。若需要对组合进行频繁地遍历或查找,Composite类可以缓冲存储对它的子节点进行遍历或查找的相关信息。Composite可以缓冲存储实际结果或者仅仅是一些用于缩短遍历或查询长度的信息。
一个组件发生变化时,它的父部件原先缓冲存储的信息也变得无效。在组件知道其父部件时,需要定义一个接口来通知组合组件它们所缓冲存储的信息无效
相关模式
责任链模式:通常部件-父部件的连接可用于责任链模式
装饰者模式:装饰者模式通常与Composite模式一起使用。当装饰和组合一起使用时,它们通常有一个公共的父类。因此装饰者必须支持具有Add、Remove和GetChild操作的
Component接口
蝇量模式:蝇量模式让你共享组件,但不能再应用它们的父部件
迭代器模式:迭代器模式可用于遍历Composite
访问者模式:访问者模式将本来应该分布在Composite和Leaf类中的操作和行为局部化
示例代码
场景
和迭代器模式场景一样,有一个饭店,饭店有一个服务员,顾客可以向服务员点菜,可以点蛋糕、晚餐、咖啡三大类,每一大类中又可以点很多小类,在顾客点完单后,服务员需要按大类和小类输出顾客的点单
这次我们使用组合模式来完成,不管是大菜单还是小菜单,它们都是菜单,对于不可继续划分的单个菜,我们也可以把它视为菜单,只不过是叶子节点而已。这样,菜单和菜就统一了,它们都可以继承自一个抽象的菜单类
我们可以定义一个组合迭代器,该迭代器是以DFS方式遍历整颗树的,对于非叶节点,就是调用它们专属的迭代器进行遍历,如果遍历到的不是叶子节点,就将当前遍历节点对应的迭代器压入栈中
Component抽象类
publicabstractclassMenuComponent { publicvoidadd(MenuComponentmenuComponent) { thrownewUnsupportedOperationException(); } publicvoidremove(MenuComponentmenuComponent) { thrownewUnsupportedOperationException(); } publicMenuComponentgetChild(inti) { thrownewUnsupportedOperationException(); } publicStringgetName() { thrownewUnsupportedOperationException(); } publicStringgetDescription() { thrownewUnsupportedOperationException(); } publicdoublegetPrice() { thrownewUnsupportedOperationException(); } publicbooleanisVegetarian() { thrownewUnsupportedOperationException(); } publicvoidprint() { thrownewUnsupportedOperationException(); } publicIteratorcreateIterator() { thrownewUnsupportedOperationException(); } }
非叶节点
publicclassMenuextendsMenuComponent { ArrayListmenuComponents=newArrayList(); Stringname; Stringdescription; publicMenu(Stringname, Stringdescription) { this.name=name; this.description=description; } publicvoidadd(MenuComponentmenuComponent) { menuComponents.add(menuComponent); } publicvoidremove(MenuComponentmenuComponent) { menuComponents.remove(menuComponent); } publicMenuComponentgetChild(inti) { return (MenuComponent) menuComponents.get(i); } publicStringgetName() { returnname; } publicStringgetDescription() { returndescription; } publicvoidprint() { System.out.print("\n"+getName()); System.out.println(", "+getDescription()); System.out.println("---------------------"); Iteratoriterator=menuComponents.iterator(); while (iterator.hasNext()) { MenuComponentmenuComponent= (MenuComponent) iterator.next(); menuComponent.print(); } } publicIteratorcreateIterator() { returnnewCompositeIterator(menuComponents.iterator()); } }
叶节点
publicclassMenuItemextendsMenuComponent { Stringname; Stringdescription; booleanvegetarian; doubleprice; publicMenuItem(Stringname, Stringdescription, booleanvegetarian, doubleprice) { this.name=name; this.description=description; this.vegetarian=vegetarian; this.price=price; } publicStringgetName() { returnname; } publicStringgetDescription() { returndescription; } publicbooleanisVegetarian() { returnvegetarian; } publicdoublegetPrice() { returnprice; } publicvoidprint() { System.out.print(" "+getName()); if (isVegetarian()) { System.out.print("(v)"); } System.out.println(", "+getPrice()); System.out.println(" -- "+getDescription()); } publicIteratorcreateIterator() { returnnewNullIterator(); } }
组合迭代器
publicclassCompositeIteratorimplementsIterator { Stackstack=newStack(); publicCompositeIterator(Iteratoriterator) { stack.push(iterator); } publicbooleanhasNext() { if (stack.empty()) { returnfalse; } else { Iteratoriterator= (Iterator) stack.peek(); if (!iterator.hasNext()) { stack.pop(); returnhasNext(); } else { returntrue; } } } publicObjectnext() { if (hasNext()) { Iteratoriterator= (Iterator) stack.peek(); MenuComponentcomponent= (MenuComponent) iterator.next(); if (componentinstanceofMenu) { stack.push(component.createIterator()); } returncomponent; } else { returnnull; } } publicvoidremove() { thrownewUnsupportedOperationException(); } }
叶节点空迭代器
publicclassNullIteratorimplementsIterator { publicbooleanhasNext() { returnfalse; } publicObjectnext() { returnnull; } publicvoidremove() { thrownewUnsupportedOperationException(); } }
外部调用者
publicclassWaitress { MenuComponentallMenus; publicWaitress(MenuComponentallMenus) { this.allMenus=allMenus; } publicvoidprintMenu() { allMenus.print(); } publicvoidprintVegetarianMenu() { Iteratoriterator=allMenus.createIterator(); System.out.println("\nVEGETARIAN MENU\n----"); while (iterator.hasNext()) { MenuComponentmenuComponent= (MenuComponent) iterator.next(); try { if (menuComponent.isVegetarian()) { menuComponent.print(); } } catch (UnsupportedOperationExceptione) {} } } }
测试代码
publicclassMenuTestDrive { publicstaticvoidmain(String[] args) { MenuComponentpancakeHouseMenu=newMenu("PANCAKE HOUSE MENU", "Breakfast"); MenuComponentdinerMenu=newMenu("DINER MENU", "Lunch"); MenuComponentcafeMenu=newMenu("CAFE MENU", "Dinner"); MenuComponentdessertMenu=newMenu("DESSERT MENU", "Dessert of course!"); MenuComponentallMenus=newMenu("ALL MENUS", "All menus combined"); allMenus.add(pancakeHouseMenu); allMenus.add(dinerMenu); allMenus.add(cafeMenu); pancakeHouseMenu.add(newMenuItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99)); pancakeHouseMenu.add(newMenuItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99)); pancakeHouseMenu.add(newMenuItem("Blueberry Pancakes", "Pancakes made with fresh blueberries", true, 3.49)); pancakeHouseMenu.add(newMenuItem("Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59)); dinerMenu.add(newMenuItem("Vegetarian BLT", "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99)); dinerMenu.add(newMenuItem("BLT", "Bacon with lettuce & tomato on whole wheat", false, 2.99)); dinerMenu.add(newMenuItem("Soup of the day", "Soup of the day, with a side of potato salad", false, 3.29)); dinerMenu.add(newMenuItem("Hotdog", "A hot dog, with saurkraut, relish, onions, topped with cheese", false, 3.05)); dinerMenu.add(dessertMenu); dessertMenu.add(newMenuItem("Apple Pie", "Apple pie with a flakey crust, topped with vanilla ice cream", true, 1.59)); dessertMenu.add(newMenuItem("Cheesecake", "Creamy New York cheesecake, with a chocolate graham crust", true, 1.89)); dessertMenu.add(newMenuItem("Sorbet", "A scoop of raspberry and a scoop of lime", true, 1.89)); cafeMenu.add(newMenuItem("Veggie Burger and Air Fries", "Veggie burger on a whole wheat bun, lettuce, tomato, and fries", true, 3.99)); cafeMenu.add(newMenuItem("Soup of the day", "A cup of the soup of the day, with a side salad", false, 3.69)); cafeMenu.add(newMenuItem("Burrito", "A large burrito, with whole pinto beans, salsa, quacamole", true, 4.29)); Waitresswaitress=newWaitress(allMenus); // waitress.printMenu();waitress.printVegetarianMenu(); } }
状态模式
定义
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类
动机
适用性
在下面的两种情况下均可使用状态模式:
- 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为
- 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化
结构
参与者
- Context
- 定义客户感兴趣的接口
- 维护一个ConcreteState子类的实例,这个实例定义当前状态
- State
- 定义一个接口以封装与Context的一个特定状态相关的行为
- ConcreteState subclasses
每一子类实现一个与Context的一个状态相关的行为
协作
- Context将与状态相关的请求委托给当前的ConcreteState对象处理
- Context可将自身作为一个参数传递给处理该请求的状态对象。这使得状态对象在必要时可访问Context
- Context是客户使用的主要接口。客户可用状态对象来配置一个Context,一旦一个Context配置完毕,它的客户不再需要直接与状态对象打交道
- Context或ConcreteState子类都可决定哪个状态是另外哪一个的后继者,以及是在何种条件下进行状态转换
结果
好:
- 它将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。State模式将所有与一个特定的状态相关的行为都放入一个对象中。因为所有与状态相关的代码都存在于某一个State子类中,所以通过定义新的子类可以很容易地增加新的状态和转换
- 它使得状态转换显式化。当一个对象仅以内部数据值来定义当前状态时,其状态仅表现为对一些变量的赋值,这不够明确。为不同的状态引入独立的对象使得转换变得更加明确。而且,State对象可保证Context不会发生内部状态不一致的情况,因为从Context的角度看,状态转换是原子的——只需重新绑定一个变量(即Context的State对象变量),而无需为多个变量赋值
坏:
- 增加了类的数量
实现
- 谁定义状态转换
State模式不指定哪一个参与者定义状态转换准则。若该准则是固定的,那么它们可在Context中完全实现。然而若让State子类自身指定它们的后继状态以及何时进行转换,通常更灵活更合适。这需要Context增加一个接口,让State对象显式地设定Context的当前状态
用这种方法分散转换逻辑可以很容易地定义新的State子类来修改和扩展该逻辑。这样做的一个缺点是,一个State子类至少拥有一个其他子类的信息,这就在各子类之间产生了实现依赖
- 创建和销毁State对象。一个常见的值得考虑的实现上的权衡是,究竟是(1)仅当需要State对象时才创建它们并随后销毁它们,还是(2)提前创建它们并始终不销毁它们
当将要进入的状态在运行时是不可知的,并且上下文不经常改变状态时,第一种选择较为可取。这种方法避免创建不会被用到的对象,若State对象存储大量的信息时这一点很重要。当状态改变很频繁时,第二种方法较好。在这种情况下最好避免销毁状态,因为可能很快再次需要用到它们。此时可以预先一次付清创建各个状态对象的开销,并且在运行过程中根本不存在销毁状态对象的开销。但是这种方法可能不太方便,因为Context必须保存对所有可能会进入的那些状态的引用
相关模式
单例模式:状态对象通常是单例
示例代码
场景
有一个机器用于卖球,用户把硬币投入机器中并转动拉杆,就会弹出一个球,用户有小概率是幸运儿,机器弹出两个球,这个机器有很多规则和状态,例如不能重复投入硬币,不能不投硬币就拉杆,即操作顺序是规定好的
机器有不同的状态,定义一个状态接口,声明用户可以执行的操作,例如投币、退币等,对于机器的不同状态,实际的表现不同,具体的状态类需要在内部维护机器的引用,机器类需要维护自己的状态,用户的操作调用的是当前状态的方法,同时方法中对机器的状态进行改变
状态接口
publicinterfaceState { voidinsertQuarter(); voidejectQuarter(); voidturnCrank(); voiddispense(); }
具体的状态
publicclassHasQuarterStateimplementsState { RandomrandomWinner=newRandom(System.currentTimeMillis()); GumballMachinegumballMachine; publicHasQuarterState(GumballMachinegumballMachine) { this.gumballMachine=gumballMachine; } publicvoidinsertQuarter() { System.out.println("You can't insert another quarter"); } publicvoidejectQuarter() { System.out.println("Quarter returned"); gumballMachine.setState(gumballMachine.getNoQuarterState()); } publicvoidturnCrank() { System.out.println("You turned..."); intwinner=randomWinner.nextInt(10); if ((winner==0) && (gumballMachine.getCount() >1)) { gumballMachine.setState(gumballMachine.getWinnerState()); } else { gumballMachine.setState(gumballMachine.getSoldState()); } } publicvoiddispense() { System.out.println("No gumball dispensed"); } }
publicclassNoQuarterStateimplementsState { GumballMachinegumballMachine; publicNoQuarterState (GumballMachinegumballMachine) { this.gumballMachine=gumballMachine; } publicvoidinsertQuarter() { System.out.println("You inserted a quarter"); gumballMachine.setState(gumballMachine.getHasQuarterState()); } publicvoidejectQuarter() { System.out.println("You haven't inserted a quarter"); } publicvoidturnCrank() { System.out.println("You turned, but there's no quarter"); } publicvoiddispense() { System.out.println("You need to pay first"); } }
Context
publicclassGumballMachine { StatesoldOutState; StatenoQuarterState; StatehasQuarterState; StatesoldState; StatewinnerState; Statestate=soldOutState; intcount=0; publicGumballMachine(intnumberGumballs) { soldOutState=newSoldOutState(this); noQuarterState=newNoQuarterState(this); hasQuarterState=newHasQuarterState(this); soldState=newSoldState(this); winnerState=newWinnerState(this); this.count=numberGumballs; if (numberGumballs>0) { state=noQuarterState; } } publicvoidinsertQuarter() { state.insertQuarter(); } publicvoidejectQuarter() { state.ejectQuarter(); } publicvoidturnCrank() { state.turnCrank(); state.dispense(); } voidrefill(intnumberGumballs) { this.count=numberGumballs; state=noQuarterState; } voidreleaseBall() { System.out.println("A gumball comes rolling out the slot..."); if (count!=0) { count=count-1; } } publicStategetWinnerState() { returnwinnerState; } publicStategetSoldOutState() { returnsoldOutState; } publicStategetNoQuarterState() { returnnoQuarterState; } publicStategetHasQuarterState() { returnhasQuarterState; } publicStategetSoldState() { returnsoldState; } publicStategetState() { returnstate; } publicintgetCount() { returncount; } publicvoidsetState(Statestate) { this.state=state; } publicvoidsetCount(intcount) { this.count=count; } publicStringtoString() { return"GumballMachine{"+"state="+state+", count="+count+'}'; } }
代理模式
定义
代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问
适用性
以下是一些可以使用代理模式的常见情况:
- 远程代理:远程代理就好比“远程对象的本地代表”。何谓“远程对象”?这是一种对象,活在不同的JVM堆中(更一般的说法为,在不同的地址空间运行的远程对象)。何谓“本地代表”?这是一种可以由本地方法调用的对象,其行为会转发到远程对象中
- 虚拟代理:虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象
- 保护代理:控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
- 智能引用代理:在访问对象时执行一些附加操作,例如当主题被引用时,进行额外的动作,例如计算一个对象被引用的次数。可用于对用户进行访问计数
结构
参与者
- Proxy
- 保存一个引用使得代理可以访问实体。若RealSubject和Subject的接口相同,Proxy会引用Subject
- 提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体
- 控制对实体的存取,并可能负责创建和删除它
- 其他功能依赖于代理的类型:
- 远程代理负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求
- 虚拟代理可以缓存实体的附加信息,以便延迟对它的访问
- 保护代理检查调用者是否具有实现一个请求所必需的访问权限
- Subject
定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy - RealSubject
定义Proxy所代表的实体
协作
代理根据其种类,在适当的时候向RealSubject转发请求
结果
Proxy模式在访问对象时引入了一定程度的间接性。根据代理的类型,附加的间接性有多种用途:
(1)Remote Proxy可以隐藏一个对象存在于不同地址空间的事实
(2)Virtual Proxy可以进行最优化,例如根据需要创建对象
(3)保护代理和智能引用代理都允许在访问一个对象时有一些附加的处理操作
(4)还有一种代理为Copy-On-Write Proxy,用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止。即保证只有当这个对象被修改时才对它进行拷贝。这是虚拟代理的变体
实现
- Proxy并不总是需要知道实体的类型。若Proxy类能够完全通过一个抽象接口处理它的实体,则无须为每一个RealSubject类都生成一个Proxy类;Proxy可以统一处理所有的RealSubject类。但是如果Proxy要实例化RealSubjects(例如在虚拟代理中),那么它们必须知道具体的类
已知应用
rmi
相关模式
适配器模式:适配器为它所适配的对象提供了一个不同的接口。相反,代理提供了与它的实体相同的接口。然而,用于访问保护的代理可能会拒绝执行实体会执行的操作,因此,它的接口实际上可能只是实体接口的一个子集
装饰者模式:尽管装饰者的实现部分与代理相似,但装饰者的目的不一样。装饰者为对象添加一个或多个功能,而代理则控制对对象的访问。
示例代码
场景
从数据库中获取一个员工的信息,并只能通过该员工的代理对象修改其状态,如果代理对象是该员工的上司,那么能直接修改其状态,否则只能查询不能修改
定义员工接口,JDK动态代理必须要有接口,因为JDK动态代理会生成一个代理类,该代理类已经继承了Proxy类,因为不能多继承,所以只能实现用户的接口。再定义代理类和接口的实现类即可,代理类中包含实际对象的引用
接口
publicinterfacePersonBean { StringgetName(); StringgetGender(); StringgetInterests(); intgetHotOrNotRating(); voidsetName(Stringname); voidsetGender(Stringgender); voidsetInterests(Stringinterests); voidsetHotOrNotRating(intrating); }
接口实现类
publicclassPersonBeanImplimplementsPersonBean { Stringname; Stringgender; Stringinterests; intrating; intratingCount=0; publicStringgetName() { returnname; } publicStringgetGender() { returngender; } publicStringgetInterests() { returninterests; } publicintgetHotOrNotRating() { if (ratingCount==0) { return0; } return (rating/ratingCount); } publicvoidsetName(Stringname) { this.name=name; } publicvoidsetGender(Stringgender) { this.gender=gender; } publicvoidsetInterests(Stringinterests) { this.interests=interests; } publicvoidsetHotOrNotRating(intrating) { this.rating+=rating; ratingCount++; } publicPersonBeanImpl(Stringname, Stringgender, Stringinterests, intrating, intratingCount) { this.name=name; this.gender=gender; this.interests=interests; this.rating=rating; this.ratingCount=ratingCount; } }
代理类
publicclassOwnerInvocationHandlerimplementsInvocationHandler { PersonBeanperson; publicOwnerInvocationHandler(PersonBeanperson) { this.person=person; } publicObjectinvoke(Objectproxy, Methodmethod, Object[] args) throwsIllegalAccessException { try { if (method.getName().startsWith("get")) { returnmethod.invoke(person, args); } elseif (method.getName().equals("setHotOrNotRating")) { thrownewIllegalAccessException(); } elseif (method.getName().startsWith("set")) { returnmethod.invoke(person, args); } } catch (InvocationTargetExceptione) { e.printStackTrace(); } returnnull; } }
publicclassNonOwnerInvocationHandlerimplementsInvocationHandler { PersonBeanperson; publicNonOwnerInvocationHandler(PersonBeanperson) { this.person=person; } publicObjectinvoke(Objectproxy, Methodmethod, Object[] args) throwsIllegalAccessException { try { if (method.getName().startsWith("get")) { returnmethod.invoke(person, args); } elseif (method.getName().equals("setHotOrNotRating")) { returnmethod.invoke(person, args); } elseif (method.getName().startsWith("set")) { thrownewIllegalAccessException(); } } catch (InvocationTargetExceptione) { e.printStackTrace(); } returnnull; } }
外部调用者
publicvoiddrive() { PersonBeanjoe=getPersonFromDatabase("Joe Javabean"); PersonBeanownerProxy=getOwnerProxy(joe); System.out.println("Name is "+ownerProxy.getName()); ownerProxy.setInterests("bowling, Go"); System.out.println("Interests set from owner proxy"); try { ownerProxy.setHotOrNotRating(10); } catch (Exceptione) { System.out.println("Can't set rating from owner proxy"); } System.out.println("Rating is "+ownerProxy.getHotOrNotRating()); PersonBeannonOwnerProxy=getNonOwnerProxy(joe); System.out.println("Name is "+nonOwnerProxy.getName()); try { nonOwnerProxy.setInterests("bowling, Go"); } catch (Exceptione) { System.out.println("Can't set interests from non owner proxy"); } nonOwnerProxy.setHotOrNotRating(3); System.out.println("Rating set from non owner proxy"); System.out.println("Rating is "+nonOwnerProxy.getHotOrNotRating()); } PersonBeangetOwnerProxy(PersonBeanperson) { return (PersonBean) Proxy.newProxyInstance( person.getClass().getClassLoader(), person.getClass().getInterfaces(), newOwnerInvocationHandler(person) ); } PersonBeangetNonOwnerProxy(PersonBeanperson) { return (PersonBean) Proxy.newProxyInstance( person.getClass().getClassLoader(), person.getClass().getInterfaces(), newNonOwnerInvocationHandler(person) ); } PersonBeangetProxy(InvocationHandlerhandler, PersonBeanperson) { return (PersonBean) Proxy.newProxyInstance( person.getClass().getClassLoader(), person.getClass().getInterfaces(), handler ); }
桥接模式
定义
使用桥接模式,不止改变你的实现,也改变你的抽象
动机
适用性
以下情况使用桥接模式:
- 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换
- 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。此时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充
- 对一个抽象的实现部分的修改应对客户不产生影响
结构
参与者
- Abstraction
- 定义抽象类的接口
- 维护一个指向Implementor类型对象的引用
- RefinedAbstraction
扩充由Abstraction定义的接口 - Implementor
定义实现类的接口,该接口不一定要与Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来说,Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作 - ConcreteImplementor
实现Implementor接口并定义它的具体实现
协作
Abstraction将client的请求转发给它的Implementor对象
结果
好:
- 分离接口及其实现部分
一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现 - 提高可扩充性
可以独立地对Abstraction和Implementor层次结构进行扩充
实现
- 仅有一个Implementor的情况,在仅有一个实现的时候,没有必要创建一个抽象的Implementor类。这是桥接模式的退化情况;在Abstraction与Implementor之间有一种一对一的关系。尽管如此,当你希望改变一个类的实现不会影响已有的客户程序时,模式的分离机制还是非常有用的
相关模式
抽象工厂模式:抽象工厂模式可以用来创建和配置一个特定的桥接模式
适配器模式:适配器模式用来帮助无关的类协同工作,它通常在系统设计完成后才会被使用。然而,桥接模式则是在系统开始时就被使用, 它使得抽象接口和实现部分可以独立进行改变
生成器模式
定义
使用生成器模式封装一个产品的构造过程,并允许按步骤构造
动机
适用性
在以下情况下使用Builder模式:
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时
- 当构造过程必须允许被构造的对象有不同的表示时
结构
参与者
- Builder
为创建一个Product对象的各个部件指定抽象接口 - ConcreteBuilder
- 实现Builder的接口以构造和装配该产品的各个部件
- 定义并明确它所创建的表示
- 提供一个检索产品的接口
- Director
构造一个使用Builder接口的对象 - Product
- 表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程
- 包含定义组成部件的类,包括将这些部件装配成最终产品的接口
协作
- 客户创建Director对象,并用它所想要的Builder对象进行配置
- 一旦产品部件被生成,Director就会通知生成器
- 生成器处理Director的请求,并将部件添加到该产品中
- 客户从生成器中检索产品
下面的交互图说明了Builder和Director是如何与一个客户协作的:
结果
- 它使你可以改变一个产品的内部表示。Builder对象提供给Director一个构造产品的抽象接口。该接口使得生成器可以隐藏这个产品的表示和内部结构。它同时也隐藏了该产品是如何装配的。因为产品是通过抽象接口构造的,你在改变该产品的内部表示时所要做的只是定义一个新的生成器
- 它将构造代码和表示代码分开。Builder模式通过封装一个复杂对象的创建和表示方式提高了对象的模块性。客户不需要知道定义产品内部结构的类的所有信息;这些类是不出现在Builder接口中的。每个ConcreteBuilder包含了创建和装配一个特定产品的所有代码。这些代码只需要写一次;然后不同的Director可以复用它以在相同部件集合的基础上构建不同的Product
- 它使你可对构造过程进行更精细的控制。Builder模式与一下子就生成产品的创建型模式不同,它是在Director的控制下一步步构造产品的。仅当该产品完成时Director才从生成器中取回它。因此Builder接口相比其他创建型模式能更好地反映产品的构造过程。这使你可以更精细地控制构建过程,从而能更精细地控制所得产品的内部结构
实现
通常有一个抽象的Builder类为Director可能要求创建的每一个构件定义一个操作。这些操作缺省情况下什么都不做。一个ConcreteBuilder类对它有兴趣创建的构件重定义这些操作
相关模式
抽象工厂模式:抽象工厂模式与Builder相似,是因为它也可以创建复杂对象。主要的区别是Builder模式着重于一步步构造一个复杂对象。而Abstract Factory着重于多个系列的产品对象(简单的或是复杂的)。Builder在最后的一步返回产品,而对于抽象工厂来说,产品是立即返回的
组合模式:组合模式通常是用Builder生成的
责任链模式
定义
当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用责任链模式
责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
动机
给多个对象处理一个请求的机会,从而解耦发送者和接收者。该请求沿对象链传递直至其中一个对象处理它
从第一个对象开始,链中收到请求的对象要么亲自处理它,要么转发给链中的下一个候选者。提交请求的对象并不明确地知道哪一个对象将会处理它(我们说该请求有一个隐式的接收者)
适用性
在以下条件下使用责任链模式:
- 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定
- 想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可处理一个请求的对象集合应被动态指定
结构
参与者
- Handler
- 定义一个处理请求的接口
- (可选)实现后继链
- ConcreteHandler
- 处理它所负责的请求
- 可访问它的后继者
- 若可处理该请求,就处理之;否则将该请求转发给它的后继者
- Client
- 向链上的具体处理者(ConcreteHandler)对象提交请求
协作
当客户提交一个请求时,请求沿链传递直至有一个ConcreteHandler对象负责处理它
结果
好:
- 降低耦合度
该模式使得一个对象无须知道是其他哪一个对象处理其请求。对象仅需知道该请求会被“正确”地处理。接收者和发送者都没有对方的明确的信息,且链中的对象不需知道链的结构。
结果是,责任链可简化对象的相互连接。它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接收者的引用 - 增强了给对象指派责任的灵活性。当在对象中分派责任时,责任链给你更多的灵活性。你可以通过在运行时刻对该链进行动态地增加或修改来增加或改变处理一个请求的那些职责
坏:
- 不保证被接收
既然一个请求没有明确的接收者,那么就不能保证它一定会被处理。该请求可能一直到链的末端都得不到处理。一个请求也可能因该链没有被正确配置而得不到处理
已知应用
Spring Security 过滤器责任链
相关模式
组合模式:责任链常与组合模式一起使用,这种情况下,一个构件的父构件可作为它的后继
蝇量模式
定义
若想让某个类的一个实例能用来提供许多“虚拟实例”,就使用蝇量模式。
适用性
蝇量模式适用于以下情况:
- 一个应用程序使用了大量的对象,造成很大的存储开销
- 对象的大多数状态都可变为外部状态
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象
结构
参与者
- Flyweight
描述一个接口,通过这个接口flyweight可以接收并作用于外部状态 - ConcreteFlyweight
实现Flyweight接口,并为内部状态(如果有的话)增加存储空间。ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的; - UnsharedConcreteFlyweight
并非所有的flyweight子类都需要被共享。flyweight接口使共享成为可能,但它并不强制共享。在flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点 - FlyweightFactory
- 创建并管理flyweight对象
- 确保合理地共享flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)
- Client
- 维持一个对flyweight的引用
- 计算或存储一个(多个)flyweight的外部状态
协作
- flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中;而外部对象则由Client对象存储或计算。当用户调用flyweight对象的操作时,将该状态传递给它
- 用户不应直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,这可以保证对它们适当地进行共享
结果
好:
- 减少运行时对象实例的个数,节省内存
- 将许多虚拟对象的状态集中管理
坏:
- 一旦实现了蝇量模式,那么单个的逻辑实例将无法拥有独立而不同的行为
相关模式
组合模式:蝇量模式通常和组合模式结合起来
解释器模式
定义
使用解释器模式为语言创建解释器
结构
中介者模式
定义
使用中介者模式来集中相关对象之间复杂的沟通和控制方式
动机
面向对象设计鼓励将行为分布到各个对象中。这种分布可能会导致对象间有许多连接。在最坏的情况下,每一个对象都知道其他所有对象
虽然将一个系统分割成许多对象通常可以增强可复用性,但是对象间相互连接的激增又会降低其可复用性。大量的相互连接使得一个对象似乎不太可能在没有其他对象的支持下工作——系统表现为一个不可分割的整体。而且,对系统的行为进行任何较大的改动都十分困难。因为行为被分布在许多对象中。结果是,你可能不得不定义很多子类以定制系统的行为
可以通过将集体行为封装在一个单独的中介者对象中以避免这个问题。中介者负责控制和协调一组对象间的交互。中介者充当一个中介以使组中的对象不再相互显式引用。这些对象仅知道中介者,从而减少了相互连接的数目
适用性
在以下情况下使用中介者模式:
- 一组对象以定义良好但是复杂的方式进行通信,产生的相互依赖关系结构混乱且难以理解
- 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象
- 想定制一个分布在多个类中的行为,而又不想生成太多的子类
结构
参与者
- Mediator(中介者)
中介者定义一个接口用于与各同事(Colleague)对象通信 - ConcreteMediator(具体中介者)
- 具体中介者通过协调各同事对象实现协作行为
- 了解并维护它的各个同事
- Colleague class
- 每一个同事类都知道它的中介者对象
- 每一个同事对象在需与其他的同事通信的时候,与它的中介者通信
协作
同事向一个中介者对象发送和接收请求。中介者在各同事间适当地转发请求以实现协作行为
结果
好:
- 减少了子类生成。Mediator将原本分布于多个对象间的行为集中在一起。改变这些行为只需生成Meditator的子类即可。这样各个Colleague类可被重用
- 它将各Colleague解耦。Mediator有利于各Colleague间的松耦合。你可以独立地改变和复用各Colleague类和Mediator类
- 用Mediator和各Colleague间的一对多的交互来代替多对多的交互。一对多的关系更易于理解、维护和扩展
- 它对对象如何协作进行了抽象。将中介作为一个独立的概念并将其封装在一个对象中,使你将注意力从对象各自本身的行为转移到它们之间的交互上来。这有助于弄清楚一个系统中的对象是如何交互的
坏:
- 中介者模式将交互的复杂性变为中介者的复杂性。因为中介者封装了协议,它可能变得比任一个Colleague都复杂。这可能使得中介者自身成为一个难以维护的庞然大物
实现
- 何时忽略抽象的Mediator类?当各Colleague仅与一个Mediator一起工作时,没有必要定义一个抽象的Mediator类。Mediator类提供的抽象耦合已经使各Colleague可与不同的Mediator子类一起工作,反之亦然
- Colleague-Mediator通信。当一个感兴趣的事件发生时,Colleague必须与其Mediator通信。一种实现方法是使用Observer模式,将Mediator实现为一个Observer,各Colleague作为Subject,一旦其状态改变就发送通知给Mediator。Mediator作出的响应是将状态改变的结果传播给其他的Colleague。
另一个方法是在Mediator中定义一个特殊的通知接口,各Colleague在通信时直接调用该接口
相关模式
外观模式:外观模式与中介者的不同之处在于外观模式是对一个对象子系统进行抽象,从而提供了一个更为方便的接口。它的协议是单向的,即外观模式对这个子系统类提出请求,但反之则不行。而Mediator提供了各Colleague对象不支持或不能支持的协作行为,而且协议是多向的
观察者模式:Colleague可使用观察者模式与中介者Mediator通信
备忘录模式
定义
当你需要让对象返回之前的状态时(例如,你的用户请求“撤销”),就使用备忘录模式
动机
有时有必要记录一个对象的内部状态。为了允许用户取消不确定的操作或从错误中恢复过来,需要实现检查点和取消机制,而要实现这些机制,你必须事先将状态信息保存在某处,这样才能将对象恢复到它们先前的状态。
备忘录模式可解决这一问题。一个备忘录(memento)是一个对象,它存储另一个对象在某个瞬间的内部状态,而后者称为备忘录的原发器(originator)。当需要设置原发器的检查点时,取消操作机制会向原发器请求一个备忘录。原发器用描述当前状态的信息初始化该备忘录。只有原发器可以向备忘录中存取信息,备忘录对其他的对象“不可见”
适用性
在以下情况下使用备忘录模式:
- 必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态
结构
参与者
- Memento
- 备忘录存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态
- 防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口,管理者(caretaker)只能看到备忘录的窄接口——它只能将备忘录传递给其他对象。相反,原发器能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。理想的情况是只允许生成本备忘录的那个原发器访问本备忘录的内部状态
- Originator(原发器)
- 原发器创建一个备忘录,用以记录当前时刻它的内部状态
- 使用备忘录恢复内部状态
- Caretaker(负责人)
- 负责保存好备忘录
- 不能对备忘录的内容进行操作或检查
结果
好:
- 保持封装边界。使用备忘录可以避免暴露一些只应由原发器管理却又必须存储在原发器之外的信息。该模式把可能很复杂的Originator内部信息对其他对象屏蔽起来,从而保持了封装边界
坏:
- 使用备忘录可能代价很高。若原发器在生成备忘录时必须拷贝并存储大量的信息,或者客户非常频繁地创建备忘录和恢复原发器状态,可能会导致非常大的开销。除非封装和恢复Originator状态的开销不大,否则该模式可能并不合适。
实现
- 存储增量式改变。若备忘录的创建及其返回(给它们的原发器)的顺序是可预测的,备忘录可以仅存储原发器内部状态的增量改变
例如,一个包含可撤销的命令的历史列表可使用备忘录以保证当命令被取消时,它们可以被恢复到正确的状态。历史列表定义了一个特定的顺序,按照这个顺序命令可以被取消和重做。这意味着备忘录可以只存储一个命令所产生的增量改变而不是它所影响的每一个对象的完整状态。
相关模式
命令模式:命令模式可使用备忘录来为可撤销的操作维护状态
迭代器模式:备忘录可用于迭代
原型模式
定义
当创建给定类的实例的过程很昂贵或很复杂时,就使用原型模式
动机
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
适用性
在以下情况下可使用原型模式:
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些
- 当一个系统应该独立于它的产品创建、构成和表示时
- 当要实例化的类是在运行时刻指定时
结构
参与者
- Prototype
声明一个克隆自身的接口 - ConcretePrototype
实现一个克隆自身的操作 - Client
让一个原型克隆自身从而创建一个新的对象
协作
客户请求一个原型克隆自身
结果
好:
- 对客户隐藏了具体的产品类,使客户无需改变即可使用与特定应用相关的类
- 运行时刻增加和删除产品。原型允许只通过客户注册原型实例就可以将一个新的具体产品类并入系统。它比其他创建型模式更为灵活,因为客户可以在运行时刻建立和删除原型
- 改变值以指定新对象。高度动态的系统允许你通过对象复合定义新的行为——例如,通过为一个对象变量指定值,并且不定义新的类。你通过实例化已有类并且将这些实例注册为客户对象的原型,就可以有效定义新类别的对象。客户可以将职责代理给原型,从而表现出新的行为。
这种设计使得用户无需编程即可定义新”类“。实际上,克隆一个原型类似于实例化一个类。原型模式可以极大地减少系统所需要的类的数目
实现
- 使用一个原型管理器。当一个系统中原型数目不固定时(也就是说,它们可以动态创建和销毁),要保持一个可用原型的注册表。客户不会自己来管理原型,但会在注册表中存储和检索原型。客户在克隆一个原型前会向注册表请求该原型。我们称这个注册表为原型管理器
原型管理器是一个关联存储器,它返回一个与给定关键字相匹配的原型。它有一些操作可以用来通过关键字注册原型和解除注册。客户可以在运行时更改或浏览这个注册表 - 实现克隆操作。原型模式最困难的部分在于正确实现Clone操作。特别是当对象结构包含循环引用时
相关模式
抽象工厂模式可以存储一个被克隆的原型集合,并且返回产品对象
大量使用组合模式和装饰器模式的设计通常也可从原型模式中获益
访问者模式
定义
访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
动机
当你想要为一个对象的组合增加新的能力,且封装并不重要时,就使用访问者模式
适用性
在下列情况下使用访问者模式:
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作
结构
参与者
- Visitor
为该对象结构中ConcreteElement的每一个声明一个Visit操作。 - ConcreteVisitor
实现每个由Visitor声明的操作。 - Element
定义一个Accept操作,它以一个访问者为参数 - ConcreteElement
实现Accept操作,该操作以一个访问者为参数 - ObjectStructure(对象结构)
- 能枚举它的元素
- 可以提供一个高层的接口以允许该访问者访问它的元素
- 可以是一个组合或是一个集合
协作
- 一个使用Visitor模式的客户必须创建一个ConcreteVisitor对象,然后遍历该对象结构,并用该访问者访问每一个元素
- 当一个元素被访问时,它调用对应于它的类的Visitor操作。如有必要,该元素将自身作为这个操作的一个参数以便该访问者访问它的状态
结果
好:
- 访问者模式使得易于增加新的操作。访问者使得增加依赖于复杂对象结构的构件的操作变得容易了。仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反,若每个功能都分散在多个类之上的话,定义新的操作时必须修改每一类
- 访问者集中相关的操作而分离无关的操作。相关的行为不是分布在定义该对象结构的各个类上,而是集中在一个访问者中。无关行为却被分别放在它们各自的访问者子类中。这就既简化了这些元素的类,也简化了在这些访问者中定义的算法。所有与它的算法相关的数据结构都可以被隐藏在访问者中
坏:
- 增加新的ConcreteElement类很困难。Visitor模式使得难以增加新的Element的子类。每添加一个新的ConcreteElement都要在Vistor中添加一个新的抽象操作,并在每一个ConcreteVisitor类中实现相应的操作。
实现
- 谁负责遍历对象结构?一个访问者必须访问这个对象结构的每一个元素。我们可以将遍历的责任放到下面三个地方中的任意一个:对象结构中,访问者中,或一个独立的迭代器对象中。
通常由对象结构负责迭代。一个集合只需对它的元素进行迭代,并对每一个元素调用Accept操作。而一个复合通常让Accept操作遍历该元素的各子构件并对它们中的每一个递归地调用Accept
另一个解决方案是使用一个迭代器来访问各个元素
相关模式
组合模式:访问者可以用于对一个由组合模式定义的对象结构进行操作
解释器模式:访问者可用于解释