设计模式学习笔记
由 杨柳依 创建于2019年11月3日,最近更新于2019年11月8日
UML类图
【矩形框】代表一个类(Class)。类图分三层:
-
第一层显示类的名称,如果是抽象类,则就用斜体显示;
-
第二层是类的特性,通常就是字段和属性;
-
第三层是类的操作,通常是方法或行为。注意前面的符号,
+
表示public,-
表示private,#
表示 protected。
【线条】代表类之间的关系。
继承:空心三角形+实线,鸟类继承动物类
实现:空心三角形+虚线,鸟类实现飞翔接口
关联:实线箭头,企鹅类知道气候类(企鹅类中引用了气候对象)
聚合:空心菱形+实线箭头,每只大雁都是属于一个雁群,一个雁群可以有多只大雁(雁群中引用了大雁数组对象)
组合:实心菱形+实线箭头,翅膀是鸟的一部分,拥有相同的生命周期(初始化鸟对象时,同时实例化翅膀对象)
依赖:虚线箭头,动物需要氧气、水(氧气和水是动物类某个方法的参数)
六大原则 + 一个法则
开放封闭原则:实现热插拔,提高扩展性。
单一职责原则:一个类只负责一个职责。
里氏代换原则:实现抽象的规范,实现子父类互相替换。
依赖倒转原则:针对接口编程,实现开闭原则的基础。
接口隔离原则:降低耦合度,接口单独设计,互相隔离。
合成复用原则:尽量使用聚合,组合,而不是继承。
迪米特法则:功能模块尽量独立。
1. 开放封闭原则
开闭原则的意思是:对扩展开放,对修改关闭。软件实体(类、模块、函数等)应该可以扩展,但不可修改。即面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。
以计算器程序为例,抽象出一个运算类,吗,每当要增加新的运算方式时,只要修改这个抽象类和增加新的运算类,不会修改已有的运算类。
2. 单一职责原则
单一职责原则,就一个类而言,应该仅有一个引起它变化的原因。
以俄罗斯方块为例,窗体显示(界面)是一个类,方块的移动控制(游戏逻辑)是另一个类,将不同的职责分离到不同的类上。
3. 里氏代换原则
里氏替换原则:子类型必须能够替换掉它们的父类型。(多态)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
4. 依赖倒转原则
也叫依赖倒置、依赖反转。这个原则是开闭原则的基础。
依赖倒转原则,A)高层模块不应该依赖低层模块,两个都应该依赖抽象;b)抽象不应该依赖细节,细节应该依赖抽象。即针对接口名称,不要对实现编程。
例如,电脑可以很容易修理,因为电脑内部都是由一些(高内聚低耦合的)配件组成,坏了换一个新的就可;但是收音机很难修理,因为收音机里面都是一些元器件耦合在一起。
5. 接口隔离原则
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
6. 合成复用原则
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
7. 迪米特法则
又称最少知道原则,是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。
例如,公司小李去维修电脑,只需要去找IT部,让IT部去找具体的人来维修电脑。
设计模式
1. 简单工厂模式
不在 GoF23种设计模式中
传入参数,由工厂对象去实例化实际的操作对象。
以计算器为例,传入操作运算符(+
、-
、*
、/
)给工厂对象,由工厂对象去创建实际的运算对象(为了便于扩展,这里不同的运算对应着不同的类,并且继承着运算类),最后得到结果。
2. 策略模式
定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户(多态)。
以超市打折为例,正常收费,打折收费和返利收费是具体策略,用一个类去管理具体策略;同时还可以与简单工厂模式结合,传入算法名称,由工厂去创建策略管理类。
【优点】
- 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
- 策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
- 公共的功能就是获得计算费用的结果GetResult,这使得算法间有了抽象的父类CashSuper。
- 简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试
3. 装饰器模式
装饰器模式,动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更加灵活。它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,在使用时要注意装饰的顺序。
以穿衣服为例,人为一个抽象类,有穿衣这个抽象方法,具体的男人类继承人抽象类,有一个抽象穿衣类(装饰器类)继承人抽象类,有具体的穿上衣类、穿外套类、穿内裤类、穿外裤类继承抽象穿衣类。先创建一个男人对象X、穿上衣对象A、穿内裤对象B等,将X设置到A中,再将A设置到B中,链式调用(注意是一层套一层的)。
4. 代理模式
代理模式,为其它对象提供一种代理以控制对这个对象的访问。
例如,王小明通过戴励给刘小红送礼物,有
Subjec类:定义了RealSubject和Proxy的公用接口,这样在任何使用RealSubject的地方都可以使用Proxy。【送礼物这一行为】
RealSubject类:定义Proxy所代表的真实实体。【王小明】
Proxy类:保存一个引用使得代理可以访问真实实体,并提供一个与RealSubject内相同的访问方法,这样代理就可以用来替代真实实体。【戴励】
5. 工厂方法模式
工厂模式方法,定义一个用于创建对象对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。
以计算器程序为例,创建一个工厂接口,然后加减乘除各建一个具体的工厂去实现这个接口,每个具体工厂负责创建具体的运算对象。
6. 原型模式
原型模式,用原型实例指定创建对象的种类,并通过拷贝这些原型对象创建新的对象。
例如,有一个抽象原型类有抽象clone方法,然后有多个具体原型类去实现该clone方法,注意【浅拷贝与深拷贝】,原型中引用的对象,拷贝的时候只会拷贝引用,需要在引用的对象中也实现拷贝方法,然后在给原型赋值的是对象的拷贝。
7. 模板方法模式
定义一个操作中的算法的骨架,将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
比如,一个抽象动物类有抽象吃食物的方法,在一些模板方法可以调用这个抽象方法(例如吃完东西才会睡觉),具体的狗类、猫类、鸟类实现具体的吃食物的方法。
8. 外观模式
外观模式,为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式完美的体现了依赖倒转原则和迪米特法则的思想。
例如股民炒股,股民需要知道多个股票的情况,耦合性过高,而把钱投在基金上,由基金来管理多支股票,用户只用与基金打交道。
【何时使用】
- 阶段1:在设计初期,有意识的将不同层的业务分离,比如经典的MVC架构;
- 阶段2:在开发阶段,子系统会因为不断的重构演化而变得越来越复杂,增加外观Facade可以提供一个简单的接口,减少它们之间的依赖;
- 阶段3:对于维护遗留系统而言,可以为新系统开发一个外观Facade类,让新系统与Facade对象交互,Facade与遗留系统交互完成所有复杂的工作。
9. 建造者模式
建造者模式(Builder),又叫生成器模式,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
例如,一个画小人程序,可以画出瘦人、胖人等,有
Product类:小人类,由多个部分组成。
Builder接口:建造小人各个部分的抽象类。
ConcreteBuilder类:具体建造者,实现Builder接口。具体实现如何画出小人的头身手脚各个部分。
Director类:指挥者,构建一个使用Builder接口的对象。用来根据用户的需求构建小人对象。
10. 观察者模式
观察者模式,又叫发布-订阅(Publish/Subscribe)模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
例如,员工上班偷懒,需要前台A或者前台B望风,有
Subject类:抽象通知者类(主题)。一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
Observer类:抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update() 方法,这个方法叫做更新方法。
ConcreteSubject类:叫做具体主题或具体通知者,将有关状态存入具体现察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
ConcreteObserver类:具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦Subject的状态发生了改变,所有的Observer都可以得到通知。
观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。
11. 抽象工厂模式
抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。解决涉及到多个产品系列的问题,有一个专门的工厂模式叫抽象工厂模式。
例如,有一个业务需要访问指定的数据库(可能会变动),数据库中又有几张表,为了便于修改数据库访问方式,这时就需要使用抽象工厂模式。
抽象工厂接口:里面应该包含所有的产品创建的抽象方法【创建数据库的抽象方法】;
具体的工厂:实现抽象工厂接口,创建具有特定实现的产品对象【实例化对应的具体数据库对象】;
抽象产品:它们都有可能有多种不同的实现【数据库的访问资源方式的抽象方法】;
具体产品:继承抽象产品,对抽象产品的具体分类的实现【具体数据库的访问资源方法】。
【改进1】反射
Java反射:通过全限定类名去加载对应类的字节码(Class)文件
Class c = Class.forName("com.loveshes.designpattern.reflect.User");
Constructor con = c.getConstructor(String.class, int.class);
User user = (User) con.newInstance("王饱饱", 20);
反射中的类名是字符串,可以采用变量,故更容易修改为不同的数据库具体产品,且不用在主程序中写switch方法。
使用DataAccess类,用反射技术,取代IFactory、SqlserverFactory和AccessFactory。
【改进2】反射 + 配置文件
将数据库名称放在配置文件中,这样只需要修改配置文件就可以修改程序的访问数据库,不用去改程序代码。
【总结】从这个角度上说,所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合。
12. 状态模式
状态模式(State),当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。
比如,上班时不同时间做不同工作就可以使用状态模式,首先有一个抽象状态类,设置多个工作状态继承抽象状态类,有一个工作类(上下文)来负责转换工作转态,传入任意一个工作状态,当满足某条件的时候,转入下一工作状态。
13. 适配器模式
适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式有两种类型,类适配器模式和对象适配器模式。类适配器通过多重继承对一个接口与另一个接口进行匹配。对象适配器模式主要结构如下:
Target:客户期待的接口
Adaptee:需要适配的类
Adapter:实现Target接口,通过在内部包装一个Adaptee对象,把源接口转换成目标接口
14. 备忘录模式
备忘录(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
例如,有一个游戏角色,可以在战斗之前保存状态,战斗失败了可以恢复到之前保存的状态。有
Originator类:发起人(游戏角色)。负责创建一个备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。Originator 可根据需要决定Memento存储Originator的哪些内部状态。
Memento类:备忘录(角色状态)。负责存储Originator对象的内部状态,并可防止Originator 以外的其他对象访问备忘录Memento。备忘录有两个接口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。
Caretaker类:管理者(负责管理状态)。负责保存好备忘录Memento(Originator创建的备忘录存在Caretaker里面),不能对备忘录的内容进行操作或检查。
Memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator可以根据保存的Memento信息还原到前一状态。
当角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。
15. 组合模式
组合模式(Composite),将对象组合成树形结构以表示部分-整体的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
比如,一个总公司有财务部、技术部、人力资源部等,还有分公司,分公司同样有财务部、技术部、人力资源部等。可以先设计一个公司的抽象类(或者接口),里面有增加、移除、显示部门、履行职责的抽象方法,然后设计具体的公司类继承该抽象类,实现这些方法,并加上children相关的方法;部门类也去继承公司抽象类,实现这些方法。创建分公司的时候同创建总公司相同。
这样,基本对象可以被组合为更复杂的组合对象,这个组合对象又可以被组合。
16. 迭代器模式
迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
当你需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,你就应该考虑用迭代器模式。当你需要对聚集有多种方式遍历时,可以考虑用迭代器模式。Java中的for-each
就是迭代器模式。
迭代器模式就是分离了集合对象的遍历行为,抽象出一个选代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。迭代器模式在访问数组、集合、列表等数据时,尤其是数据库数据操作时,是非常普遍的应用,但由于它太普遍了,所以各种高级语言都对它进行了封装。
17. 单例模式
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。
让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法(静态方法)。
单例模式有多种实现方法,参考单例模式 | 菜鸟教程
一般采用饿汉式在类直接创建静态对象:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
如果明确需要使用懒加载,可以采用登记式/静态内部类方式:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
否则使用双检锁/双重校验锁方式:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
18. 桥接模式
桥接模式(Bridge),将抽象部分与它的实现部分分离,使它们都可以独立地变化。(实现指的是抽象类和它的派生类用来实现自己的对象)。
实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。
例如为不同的手机开发游戏,可以将手机硬件和软件分离,手机有A、B两个品牌,软件有M、N两类。有一个抽象手机类,A手机、B手机继承抽象手机类;有一个抽象软件类,M软件、N软件继承抽象软件类;然后抽象手机类引用抽象软件类。这样不管增加手机还是增加软件都很方便。
合成/聚合复用原则(CARP):尽量使用合成(组合) / 聚合,尽量不要使用类继承。
19. 命令模式
命令模式(Command),将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
例如点烧烤,我们是客户端,服务员是Invoker,向厨师Receiver发送命令,其中有一个抽象的Command类用于声明执行操作的接口,ConcreteCommand类继承Command类,(绑定接收者厨师Receiver)用于实现具体的动作。服务员Invoke是通过Command类来向厨师Receiver进行交互的。
【优点】
- 能较容易地设计一个命令队列;
- 需要的情况下,可以较容易地将命令记入日志;
- 允许接收请求的一方决定是否要否决请求。
- 可以容易地实现对请求的撤销和重做;
- 由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易;
- 命令模式把请求一个操作的对象(服务员)与知道怎么执行一个操作的对象(厨师)分割开。
敏捷开发原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。
如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。
20. 职责链模式
职责链模式(Chain of Responsibility),使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
比如,员工请假,不同管理者级别能处理的请假天数不一样。设置一个管理者抽象类,有设置上级(继任者)方法和处理请求的抽象方法,有多个级别的管理者继承该抽象类,实现处理请求的抽象方法。这样,员工(客户端)每次都是向组长请假,组长批不了的就交由部门经理处理,经理处理不了的就由总监处理。
21. 中介者模式
中介者模式(Mediator),用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
例如,联合国安理会调节国家冲突。有一个联合国抽象类,里面有调节抽象方法(参数为调节信息和调节对象),有具体的安理会继承联合国抽象类,实现具体方法;有国家抽象类,需要设置中介者,有具体国家类继承国家抽象类,实现具体方法;在实际使用时,由中介者去调用国家类的方法。
中介者模式一般应用于一组对象以定义良好但是复杂的方式进行通信的场合,比如刚才得到的窗体Form对象或Web页面aspx,以及想定制一个分布在多个类中的行为,而又不想生成太多的子类的场合。
【优点】
-
Mediator的出现减少了各个Colleague的耦合,便得可以独立地改变和复用各个Colleague类和Mediator。
-
由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到它们之间的交互上来,也就是站在一个更宏观的角度去看待系统。
【缺点】
- 由于ConcreteMediator 控制了集中化,于是就把交互复杂性变为了中介者的复杂性,这就使得中介者会变得比任何一个ConcreteColleague都复杂。
22. 享元模式
享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。
比如为不同人创建类似的网站,可以把网站的公共部分抽取出来,将用户设置为外部状态传进去。这样的话,就算要为10个用户创建网站,也可以只有一个网站实例。有用户类,有一个抽象网站类,里面有抽象使用方法(传入用户),有多个具体的网站类,实现抽象方法;有一个网站工厂负责管理网站(没有则创建,有则取出返回)。
如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。
【优点】
- 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
- 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
【缺点】
- 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
- 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
Java中的字符串(会放到字符串池中)就是用了享元模式。
23. 解释器模式
解释器模式(interpreter),给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
比如正则表达式,可以用特定的文法去匹配字符串。
AbstractExpression类:抽象表达式。声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。
TerminalExpression类:终结符表达式。实现与文法中的终结符相关联的解释操作。实现抽象表达式中所要求的接口,主要是一个interpret() 方法。文法中每一个终结符都有一个具体终结表达式与之相对应。
NonterminalExpression类:非终结符表达式,为文法中的非终结符实现解释操作。对文法中每一条规则 R1、R2……Rn 都需要一个具体的非终结符表达式类。通过实现抽象表达式的interpret() 方法实现解释操作。解释操作以递归方式调用上面所提到的代表R1、R2……Rn中各个符号的实例变量。
Context类:包含解释器之外的一些全局信息。
24. 访问者模式
访问者模式(Visitor),表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
比如,有男人、女人2种数据结构,每类人都有多个情绪,情绪对应的操作不同。有抽象访问者类Visitor,为该对象结构中ConcreteElement的每一个类声明一个Visit操作;ConcreteVisitor1和ConcreteVisitor2类,具体访问者,实现每个由Visitor声明的操作;人抽象类Element,定义一个Accept操作,它以一个访问者为参数;男人类 ConcreteElementA和女人类ConcreteElementB,具体元素,实现Accept操作;人的集合ObjectStructure类,能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素。
Accept操作充分利用双分派技术,实现处理与数据结构的分离。
在客户程序中将具体状态作为参数传递给男人类完成了一次分派,然后男人类调用作为参数的具体状态中的方法男人反应,同时将自己(this)作为参数传递进去。这便完成了第二次分派。双分派意味着得到执行的操作决定于请求的种类和两个接收者的类型。接受方法就是一个双分派的操作,它得到执行的操作不仅决定于状态类的具体状态,还决定于它访问的人的类别。
访问者模式适用于数据结构相对稳定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。
【优点】
- 增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。
【缺点】
- 增加新的数据结构变得困难。
访问者模式的目的是要把处理从数据结构分离出来。很多系统可以按照算法和数据结构分开,如果这样的系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算法操作的增加变得容易。反之,如果这样的系统的数据结构对象易于变化,经常要有新的数据对象增加进来,就不适合使用访问者模式。
设计模式总结[23种]
1. 创建型模式
创建型模式 --> 对象怎么来
抽象工厂模式:提供一个创建一系列或相关依赖对象的接口,而无需指定它们具体的类。
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂模式使一个类的实例化延迟到其子类。
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
创建型模式隐藏了这些类的实例是如何被创建和放在一起,整个系统关于这些对象所知道的是由抽象类所定义的接口。这样,创建型模式在创建了什么、谁创建它、它是怎么被创建的,以及何时创建这些方面提供了很大的灵活性。
2. 结构型模式
结构型模式 --> 对象和谁有关
适配器模式:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
装饰器模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更加灵活。
外观模式:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
享元模式:运用共享技术有效地支持大量细粒度的对象。
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
3. 行为型模式
行为型模式 --> 对象与对象在干嘛
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
模板方法模式:定义一个操作的算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;可以对请求排队或记录请求日志,以及支持可撤销的操作。
状态模式:允许一个对象在其内部状态改变时改变它的行为,让对象看起来似乎修改了它的类。
职责链模式:多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
中介者模式:一个中介对象来封装一系列的对象交互。中介者使各对像不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
访问者模式:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换,使得算法可独立于使用它的客户而变化。
备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。