编程设计模式
六大基本原则
总原则:开闭原则(Open Close Principle,OCP)
开:对拓展开放,允许拓展。
闭:对修改关闭,禁止修改。
其含义在于拓展程序时,不要修改原有的代码,而是仅拓展。这是为了使程序的拓展性更好,易于维护和升级,当然,这样会导致代码臃肿,但利远远大于弊。
主要思想:在于使用接口和抽象类。
1:单一职责原则
每个类应该实现单一的职责,若存在不同种修改的原因,应该将对象进行拆分。
2:里氏替换原则(Liskov Substitution Principle,LSP) /* 子可替父,正常工作 */
面向对象设计的基本原则之一。其主要思想在于任何基类可以出现的地方,子类一定可以出现。其作为开闭原则的补充,描述了对实现抽象化的具体步骤的规范。
只有当派生类可以替换基类,且软件功能不受影响时,基类才能真正被复用。当然,衍生类能够在基类的基础上增加新的行为。
3:依赖倒转原则(Dependency Inversion Principle,DIP) /* 接口交互 ,不看具体 */
作为开闭原则的基础,在面向接口编程时,依赖于抽象类,而非具体的实现类。写代码中,用到具体类时,不与具体类进行交互,而是与其接口进行交互。
4:接口隔离原则(Interface Segregation Principle,ISP) /* 细分接口,功能纯粹 */
每个接口不存在子类不需要,但必须实现的方法。如果有,就要将接口拆分。使用多个隔离接口,比使用单个(合多为一)接口更好。
5:迪米特原则(最少知道原则)(Demeter Principle,DP)
一个类A,对要用到的类B的信息(如作为其成员变量),知道的越少越好。无论对类B本身有多复杂,都应该只使用public来使用它,将其复杂的逻辑封装在方法的内部。这样在变动时,对类A的影响会最小。
另一个陈述方式是:只与直接的朋友通信。
类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。若类B作为类A成员变量、方法参数、方法返回值,则为直接朋友;若作为函数内局部变量、临时变量则非直接朋友。陌生的类,不要作为局部变量出现在类中。
6:合成复用原则(Composite Reuse Principle,CRP)
尽量首先使用合成/聚合的方式,而不是使用继承。先考虑将类作为成员变量、函数参数,而不是先考虑继承。
java的23种设计模式
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
创建型模式
概述:用什么方法创建类和对象。
包含:工厂方法模式、抽象工厂模式、单例模式、原型模式、建造者模式。
0.简单工厂模式(不属于23种之一)
以其,可以延伸出更好的方法,所以先探讨这个。适用于:大量的产品(接口的具体类)需要创建。
三种简单工厂模式:普通、多方法、静态方法。
以下是在测试Test类中,调用了工厂类和接口类。以接口类作为基类,标识符指向实现对象:工厂类中函数(produce)返回的接口实例。
Test类中 | 普通 | 多方法 | 静态方法 |
---|---|---|---|
接口类(负责指向具体对象) | 需要实例化。指向工厂produce返回对象。 | 需要实例化。指向工厂produce返回对象。 | 需要实例化。指向工厂produce返回对象。 |
工厂类(生成接口实例) | 需要实例化。一个produce函数。需要参数,来进行对接口的选择。可能要具备 | 需要实例化。多个produceX函数。每个返回不同的接口实现对象。 | 不需要实例化。也是多个produceX。负责返回单一的接口实现对象,但其为静态函数。 |
可以看出,静态工厂方法,比前两种更加优越。其不需要传入参数,所以不必考虑参数不符合的纠错。比多方法又不需要实例化。
1.工厂方法模式(以下属于23种)
作为简单工厂的最终实现,其根据代码 “不修改,只拓展” 的原则:
①有工厂接口。派生出多个具体工厂类,每厂只生产一种对象。
②有产品接口(如Sender,具体实现为EmailSender、SmsSender等)。派生出多个具体产品类。每个具体产品都有唯一工厂创建。
当拓展时,新建一个产品类,就对应新建一个工厂类。不修改,只拓展。
反思:这种思想会导致大量臃肿的代码,应配合其他方法使用,而非独一。
2.抽象工厂模式
工厂方法模式:更像现实中工厂的流水线,而非工厂。因为一个具体工厂类,只能生产一个产品。
抽象工厂模式:更像现实中工厂。其有多个抽象工厂,每个工厂,能生产一个接口下的不同具体实例。
对比 | 工厂接口 | 具体工厂 | 产品接口 | 具体产品 |
---|---|---|---|---|
工厂方法模式 | 1个 | k个 | 1个 | k个 |
抽象方法模式 | n个 | n或n*k个 | k个 | n*k个 |
抽象方法模式中,每个具体工厂可以生产一个产品(简单工厂模式),或者生产多个产品(工厂方法模式)。
当然,如采用工厂方法模式(不修改,仅拓展),会导致很多的类,采用简单工厂模式会更加简洁(修改并拓展)。
3.单例模式
单例对象是一种常用的设计模式。可保证在jvm中,每个对象实例仅存在一个(一个类只可以产生同一个对象)。
class A{}
public class Singleton {
private Singleton() {};
public static Singleton getInstance() {
return SingleFactory.instance;
}
private static class SingleFactory{
private static Singleton instance =new Singleton();
};
public static void main(String [] args) {
Singleton x =Singleton.getInstance();
Singleton y=Singleton.getInstance();
System.out.println("x=y ? "+(x==y));/*使用了单例模式,x、y指向同一实例:输出true*/
A a = new A();
A a1 = new A();
System.out.println("a=a1 ? "+(a==a1));/*正常实例化的两个对象,a、a1指向不同实例:输出 false8*/
}
}
优点:
①某些类尤其是大型类,当其频繁的创建时,对系统开销很大。
②省去了new操作符,降低了系统内存的使用频率,减轻GC(垃圾回收)压力。
③某些类,如交易所的核心交易引擎,只能有一个存在,不能创建多个,否则会出大错。使用单例可以避免出现这些麻烦。
实现:
①将类构造函数变成私有,禁止自动构造。
②提供一个getInstance()方法,返回该类对象。
③getInstance()函数中,对获取对象的方法,要注意多线程出错问题,可采用synchronized进行必要锁定。
实例可以看:https://www.cnblogs.com/onetwo/p/9933417.html
4.建造者模式
Builder和Factory不同之处,在于Builder可以将多种类型,复合起来:其内部可以包含一个或多个List<Interface/abstract>。
一个List<Interface/abstract>:又可以包含多个不同的具体派生类、实现类实例。
如同翁凯老师在java课中讲的FoxAndRabbit实例,Filed类中,包含了Cell [ ] [ ] Animals的数组。里面可以存放狐狸、兔子或空。动物都implements (interface) Cell的。
5.原型模式(Prototype)
概述:用复制clone()代替new,在类中有大量对象时,复制比new一个对象更高效。
原型模式通常适用于以下场景。
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 对象的创建过程比较麻烦,但复制比较简单的时候
实现:
①该原型类需要implements Cloneable。
②重写clone()函数,通常直接return super.clone();
③测试中,对象不新建,而是复制。 x != x.clone();
浅拷贝:拷贝时创建一个新的对象,对象中的基本数据类型会复制,而对属性是引用类型(如对象成员变量)不会创建新的对象,而是使用与之前的对象指向同一个内存地址。即:
x!=x.clone();
x.b == x.clone().b; /* b为class B类型的静态成员变量 */
深拷贝:拷贝时创建一个新的对象,并且所有属性都是新对象(指向新的内存地址)
结构型模式
概述:用什么方法将类和对象布局(组合复用)成更大的结构。
分为:类结构型模式和对象结构型模式。前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
由于组合关系或聚合关系,比类的继承关系耦合性低,所以通常对象结构型模式具备更强灵活性。
1.适配器模式
在软件设计中可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。
分为:类适配器、对象适配器(更多用)。
主要优点:
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
其缺点是:对类适配器来说,更换适配器的实现过程比较复杂。
方式:如main函数中需要调用request()函数,而数据库里有个可以实现这个功能的函数,但其接口为SpecialRequest(),此时,我们将SpecialReaquest()函数进行包装,用继承或包含类对象的方式,给出一个request接口,里面调用SpecialRequest()。
其他模式都是从适配器模式,延伸出来的。
2.装饰模式
对已有的类,进行功能拓展。
方法:建立一个新类,与原有类要implements same interfaces,然后重写方法,方法中,可以调用已有类的同名方法,并对其拓展。
3.代理模式(Proxy)
对某个类A进行代理,用新的代理类B接口来替换其接口,当需要对A进行访问时,不直接访问A,而是通过B访问A。
是为了对被代理类,进行保护,如访问远程对象很大(视频、大图),或服务器中的数据库,出于安全不能让客户端直接访问,而是通过中间类。
主要优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能; (拓展不修改)
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
主要缺点:
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
应用场景:
- 远程代理:这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
- 虚拟代理:这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
- 安全代理:这种方式通常用于控制不同种类客户对真实对象的访问权限。
- 智能指引:主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
- 延迟加载:指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。
4.外观模式(Facade)
迪米特原则(最少知道原则)的具体实现。将多个相关联类,组合到一个类Conbine中(以成员变量等形式)。Conbine类提供统一的接口,对每个组件类统一操作,降低了客户端和组件类的耦合性,也降低了组件类之间互相关联的耦合性。
举例:Computer类包含了Cpu类、Memory类、Disk类。Computer::start中,会调用组件类的start函数。
应用场景
对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
限制:
不能很好地限制客户使用子系统类。
增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
5.桥接模式(Bridge)
概述:用组合关系代替继承关系。
优点:
- 由于抽象与实现分离,所以扩展能力强;
- 其实现细节对客户透明。
缺点:
由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。
如:对于女士包:颜色、型号、挎包、钱包。
不是对女士包进行一个抽象,具体实现时包含各自的款型和颜色。而是:提出颜色类、包型类(派生出钱包、挎包)。对于每个具体的包,会包含一个颜色类的实体。
用组合代替了继承。
应用场景:
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
6.组合模式(Composite)
概述:将对象组合成形如tree的关系。如树结构便是用了组合模式。(部分-整体形式)
主要优点:
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
主要缺点:
设计较复杂,客户端需要花更多时间理清类之间的层次关系;
不容易限制容器中的构件;
不容易用继承的方法来增加构件的新功能;
7.享元模式(Flyweight)
概述:当需要多次生成对象,且他们仅有部分不同(如五子棋中的棋子,显示器中的像素点)。建立共享对象,使新建对象的相同部分指向其对象中元素。(通常与工厂模式搭配)
优点:相同对象只保存一份。降低数量,降低内存压力。
缺点:
- 为使对象共享,需使对象中区别元素可视化,外部可访问,增加程序复杂性。
- 读取该模式下的外部状态,会使运行时间稍微变长。
行为型模式
概述:行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式(*),前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。
示意图
1.策略模式(Strategy)
概述:可理解为选择模式。一个类中,针对某一个函数,可以选择不同的对象来实现(同一个基类或接口),通过输入的参数来确定。如输出“x+y”、“x*y”,可先解析出操作符,然后调用不同方法来实现;还有现实中的超市活动,可以通过送积分、送现金等方法。
主要优点:
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
主要缺点:
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类。
2.模板方法模式(Template Method)
概述:一个抽象类,很多派生类。通过对抽象类的调用,使用派生类的重载方法。
3.观察者模式(Obsever)
概述:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
如同Excel中,数据变化,会导致示意图(折线图、饼状图)发生变化。
包含此模式以后的四个模式,都是类与类(对象)之间的关系,不涉及继承。
实现:
- 抽象主题角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。(Subject)
- 具体主题角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。(Concrete Subject)
- 抽象观察者角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。(Observer)
- 具体观察者角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。(Concrete Observer)
4.迭代器模式(Iterator)
概述:建立一个迭代器对象来管理数据。
它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含了迭代器。
主要优点:
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 遍历任务交由迭代器完成,这简化了聚合类。
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
主要缺点:增加了类的个数,这在一定程度上增加了系统的复杂性。
迭代器模式主要包含以下角色。
- 抽象聚合角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。(Aggregate)
- 具体聚合角色:实现抽象聚合类,返回一个具体迭代器的实例。(ConcreteAggregate)
- 抽象迭代器角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。(Iterator)
- 具体迭代器角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。(Concretelterator)
5.责任链模式(Chain of Responsibility)
概述:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。
责任链模式是一种对象行为型模式。
主要优点:
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
主要缺点:
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
6.命令模式(Command)
概述:将一个请求封装为一个对象,使发出请求和执行请求的对象分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
在软件开发系统中,常常出现“方法的请求者”与“方法的实现者”之间存在紧密的耦合关系。这不利于软件功能的扩展与维护。例如,想对行为进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与方法的实现者解耦?”变得很重要,命令模式能很好地解决这个问题。
主要优点:
- 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
主要缺点:
可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
7.备忘录模式(Momento)
概述:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。“备份-恢复”模式
每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。
其实很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
备忘录模式是一种对象行为型模式。
类 | 起源类 | 备忘录类 | 存储备忘录类 |
---|---|---|---|
功能 | 提供一个可恢复的接口,将参数设置替换自己的参数 | 保存起源类对象必需的参数 | 存储了多条备忘录,可以多次撤销 |
主要优点:。
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
主要缺点:
资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
实现:
- 发起人角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。(Originator)
- 备忘录角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。(Memento)
- 管理者角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。(Caretaker)
8.状态模式(State)
概述:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
在软件开发过程中,应用程序中的有些对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态会发生改变,从而使得其行为也随之发生改变。如人的情绪有高兴的时候和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。
对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 语句来做状态判断,再进行不同情况的处理。但当对象的状态很多时,程序会变得很复杂。而且增加新的状态要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。
主要优点:
- 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
主要缺点:
- 状态模式的使用必然会增加系统的类与对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
实现:
- 环境角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。(Context)
- 抽象状态角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。(State)
- 具体状态角色:实现抽象状态所对应的行为。(Concrete State)
9.访问者模式(Visitor)
概述:将用于某稳定数据结构的不同操作,提取出来为单独的类。使其在不改变数据结构代码的同时,能够添加新的行为操作,为其中数据节点能提供多种访问方式。
它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。
这样的例子还有很多,例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。
访问者(Visitor)模式是一种对象行为型模式。
主要优点:
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
主要缺点:
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
实现:
- 抽象访问者角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。(Visitor)
- 具体访问者角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。(ConcreteVisitor)
- 抽象元素角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。(Element)
- 具体元素角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。(ConcreteElement)
- 对象结构角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。(Object Structure)
10.中介者模式(Mediator)
概述:定义一个中介类来封装一些列对象(类)之间的交互,降低对象间的耦合性,且可以独立改变他们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。中介者模式是一种对象行为型模式,
在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须告诉其他所有的朋友修改,这叫作“牵一发而动全身”,非常复杂。
如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参加工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。
在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者;还有大家常用的 QQ 聊天程序的“中介者”是 QQ 服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的耦合性,提高系统的灵活性。
主要优点:
- 降低了对象之间的耦合性,使得对象易于独立地被复用。
- 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
主要缺点:
当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护
实现:
- 抽象中介者角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。(Mediator)
- 具体中介者角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。(ConcreteMediator)
- 抽象同事类角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。(Colleague)
- 具体同事类角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。(Concrete Colleague)
11.解释器模式(Interpreter)
http://c.biancheng.net/view/1402.html
用于面向对象的编译解释器。