设计模式

这里有个很详细的总结

注明:本文是在看了以上文章之后,撰写的思考与总结。

一、七大面向对象设计原则

1、开闭原则(Open Closed Principle,OCP)

对扩展开放,对修改关闭。
当需求发生变化时,不改变原有代码,只通过扩展功能来满足需求。
是面向对象程序设计的终极目标。

主要通过使用接口、抽象类来实现。
抽象层稳定性高,只要设计合理,基本可以保持架构的稳定;当需求发生变化,只需要派生一个新的实现类来扩展功能即可。

优点:
提高系统的稳定性。
测试时,可以只对扩展的部分进行测试。
提高代码复用性。

2、里氏替换原则(Liskov Substitution Principle,LSP)

在所有父类出现的地方,都可以用其子类代替。
也就是说,子类尽量不要重写父类的方法。
该原则也是开闭原则的一种实现方式。

优点:
克服了子类重写父类方法后可复用性变差的问题。
可以保证不会给原系统引入错误。
(究其原因,都是因为不重写方法)

当程序因为违背了里氏替换原则,而发生了错误,有两种解决方法:
(1)取消他们的继承关系,重新设计一个他们共有的基类;
(2)修改关系为组合/聚合关系。

3、依赖倒置原则(Dependence Inversion Principle,DIP)

面向接口编程,而非面向实现编程。
也是开闭原则的一种实现方式。

因为抽象层(接口、抽象类)较为稳定,所以先使用抽象层进行规范,不涉及他们的具体实现。
每个类都应该提供相应的接口 / 抽象类,或者两者兼具。
栗如,使用接口时,在入参处传入接口类型,则以后就能传入它的任一实现类,提高了复用性。

优点:
提供程序的可读性、可维护性。
降低类间的耦合性。
提高系统的稳定性。
减少并行开发的风险。

4、单一职责原则(Single Responsibility Principle,SRP)

降低类/方法的粒度,一个类应该有且只有一个引起它变化的原因。
当职责太多时:
一个职责的变化可能会抑制会削弱其他职责;
当用户要使用某个职责,会不得不把其它职责也囊括进来。

优点:
提高了内聚性,降低了耦合性。
降低了代码复杂性、从而提高了可读性、可维护性。
变更时的风险降低。

5、接口隔离原则(Interface Segregation Principle,ISP)

一个类对另一个类的依赖应该建立在最小接口上 。
也就是说,要为每个类建立它们专用的接口 ,而不是建立一个庞大的接口 ,让所有类一起使用。

区别于单一职责原则:
单一职责原则注重的是职责,接口隔离原则注重的是对接口的依赖。
单一职责原则针对的是内部的实现细节,接口隔离原则针对的是抽象层,即项目架构。

优点:
提高了内聚性、降低了耦合性。
提高系统的层次性。
合适的接口粒度让系统更灵活 、简单。
实现类不要被迫实现不需要的方法,减少代码冗余。

6 、迪米特法则(Law of Demeter,LoD)/ 最少知识原则(Least Knowledge Principle,LKP)

两个“非朋友”之间不应该直接的相互调用 ,该采用第三方中介。(经纪人)
朋友:
当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等。

过度使用会导致中介类过多,提高系统复杂性;应反复权衡,保证系统点结构清晰。

优点:
降低耦合性。
提高可扩展性。

7 、合成复用原则(Composite Reuse Principle,CRP),又叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)

(通常类的复用分为继承复用和合成复用两种,)
进行软件复用时,要先考虑利用组合/聚合等关联关系实现,再考虑利用继承关系实现。
合成复用:
将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。

三种方式:
(1)简单依赖,将B类对象作为A类的成员方法参数传入;
(2)聚合关系,在A类中使用set方法将B类对象传入;
(3)组合关系,直接在A类中new一个B类对象。

因为继承复用:
会破坏父类的封装性,父类对子类是透明的。
提高了父类跟子类之间的耦合性。
降低子类的灵活性。父类的实现在编译时就已经确定。

而合成复用:
不破坏封装性。新对象看不到成分对象的内部细节 ,又称“黑箱复用”。
新旧对象的耦合性低,新对象存取成分对象的唯一方法是通过成分对象的接口。
灵活性高。这种复用可以在运行时动态进行。

二 、三大类型设计模式简介

1 、创建型设计模式

主要用于描述对象的创建。
关注点在于将对象的创建与使用相分离。以降低系统的耦合度。

其中,工厂方法模式属于类创建型模式,其他的全部属于对象创建型模式。

主要分为:
单例(Singleton)模式:某个类将构造方法设为私有,自己在内部生成一个实例,并提供一个全局访问点供外部获取该实例。其拓展是有限多例模式。单例模式有懒汉、饿汉、枚举、DCL等。

原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。

工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。

抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。

建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

2 、结构型设计模式

关注点在于如何将类或对象组成更大的结构。

分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
适配器模式分为类结构型模式和对象结构型模式两种,其他的全部属于对象结构型模式。

分为以下 7 种:

代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。

装饰(Decorator)者模式:动态地给对象增加一些职责,即增加其额外的功能。

外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。

组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

3 、行为型设计模式

关注点在于如何使多个类和对象协作,完成单个对象无法完成的任务。
涉及算法和职责分配。

行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。
除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。

包含以下 11 种模式:

模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。

命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。

职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。

状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。

观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。

中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。

**迭代器(Iterator)模式:**提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。

备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。

三 、创建型设计模式

对象创建型模式:

1 、单例模式(Singleton)

一个类只存在一个实例。

实现条件:
1)构造方法私有;
2)自行创建实例;
3)对外提高一个全局访问的接口获取实例。

实现方式:
懒汉(延迟加载)
饿汉(不管用不用,先给他new一个)
DCL(懒汉的一种,直接在方法加synchronized的一种优化)
枚举(利用枚举类型天生的单例特性)
内部类(懒汉的一种,需要时再加载内部类,从内部类获取实例)

注意:多线程时要按需使用synchronized跟volatile

优点:节省资源,保持内容一致性。

应用栗子:Windows 的回收站、任务管理器、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。

单例模式结构图,图源于文章首部链接

扩展:
有限多例模式:
使用列表放置多个对象,用户提供其提供的接口获得随机一个对象。
有限的多例模式结构图,图源于文章首部链接

2 、原型模式(Prototype)

对已存在的实例进行拷贝,生成一个与原对象相同或者相似的对象,其种类由原型实例确定。

适用于当对象的创建比较麻烦耗时,且新旧对象较相似/相同的时候。

分为 深拷贝 & 浅拷贝:
浅拷贝:拷贝基本类型的数据的值,引用类型数据的引用(亦即两者是互相牵连的)
深拷贝:拷贝基本类型的数据的值,引用类型数据的内容(在浅拷贝的基础上,引用类型数据需要手动拷贝,使用【新对象.属性 = this.属性.clone()】的方式)

角色:
1)声明具有克隆功能的抽象原型类或接口(如果要使用到Java的clone()的话,这个必须有,标志该类对象可以被克隆。因为克隆需要jvm支持,所以重写clone方法时一般会加入super.clone() )
2)具体原型类
3)访问类

实现:
令要进行拷贝的原型类实现具有标志性的接口 / 抽象类如Cloneable,然后重写clone()方法,在方法中进行相应的深拷贝or浅拷贝。
在访问类中使用 【已有原型实例.clone()】即可得到新对象。

原型模式结构图,图源于文章首部链接

扩展:
带原型管理器的原型模式:
添加一个管理器类,其利用一个Map容器来存储原型对象,提供给外界get / set方法,可以往容器里取得某个原型对象的克隆体,也可以往容器里放入原型对象。

带管理器的原型模式,图源于文章首部的链接

3 、工厂方法模式(FactoryMethod)

通过工厂对象来间接获得产品对象的模式。
定义一个创建产品的工厂接口,将对产品对象的创建推迟到工厂的实现类中。

与简单工厂模式的区别:
简单工厂模式没有抽象工厂类,不符合依赖倒置原则,当有多个工厂时,就要对代码进行修改,不符合开闭原则。

角色:
1)抽象工厂
2)具体工厂
3)抽象产品
4)具体产品
5)访问者

实现:
定义一个抽象产品接口,包含所有产品的共有方法;
根据需要定义具体产品类,实现抽象产品接口;
定义一个抽象工厂接口,包含一个获取产品的方法;
根据需要定义具体工厂类,实现抽象工厂接口。
访问者通过访问工厂方法,获取产品对象。

优点:
只要知道工厂就能得到产品,无需知道其创建过程;
可以根据需要随时添加产品 / 工厂,符合开闭原则。

缺点:
当工厂、产品越来越多时,结构变得复杂。

工厂方法模式结构图,图源于文章首部链接

4 、抽象工厂模式(AbstractFactory)
:
在工厂方法模式中,我们的工厂只能生产一种产品;但是在现实生活中,每个厂家的产品其实是多种多样的。
因此,我们需要一个可以生成多种产品的工厂。即,抽象工厂模式。
把同个工厂能生产的所有产品种类称为一个“产品族”。

角色:(与工厂方法模式相同)
1)抽象工厂
2)具体工厂
3)抽象产品
4)具体产品
5)访问者

优点:
客户可以直接通过工厂得到多种产品,无需知道创建过程;
当增加产品族(即工厂)时,符合开闭原则;

缺点:
当增加产品时,要对所有的工厂类进行修改,不符合开闭原则。

抽象工厂模式结构图,图源于文章首部链接

5 、建造者模式(Builder)

有些对象是由多个组成部分组成的复杂对象,可以把这些对象的组成和表示相分离,使得相同的建造流程可以表示不同的对象。

角色
指挥者(Director)
抽象建造者(Builder)
具体建造者(Concrete Builder)
产品角色(Product)
访问者

实现:
产品有多个部分组成,
抽象建造者包含了每个部分的构建方法,
具体建造者实现抽象建造者中的每个方法,
指挥者调用某个具体建造者进行来对产品进行装配。

优点:
用户无需知道产品的构造,只要选择某个建造者。
建造者相互独立,利于扩展。

缺点:
如果产品的内部实现复杂多变,会需要很多的建造者。
产品的组成部分固定了,限制了适用范围。

建造者模式结构图,图源于文章首部链接
扩展:
当只有一个建造者时,可以省去指挥者、抽象建造者…

四 、结构型设计模式

6、适配器模式(Adapter)

当我们需要的实现某种功能的组件已经存在,却与现有系统的接口不匹配时,可以使用一个“适配器”,来对其进行使用。

角色:
适配者(已有)
适配器
目标接口(想要)
访问者

实现:
1)类适配器:
让适配器继承适配者、实现目标接口,从而将旧接口转换为目标接口;用户通过适配器调用目标接口。
2)对象适配器:
让适配器实现目标接口,组合适配者,从而将旧接口转换为目标接口。(耦合度更低)

优点:
解决了问题;
实现了代码的重用。

类适配器模式结构图,图源于文章首部链接
对象适配器模式结构图,图源于文章首部链接

扩展:
将目标接口实现,
适配器中不仅写入适配者,目标接口,还写入目标接口实现类、旧接口,
以此来达到双向匹配的效果。
(个人感觉冗余,难道是为了兼容性??)
扩展对象适配器模式结构图,图源于文章首部链接

7、桥接模式(Bridge)

将抽象与实现相分离,使用组合关系来代替继承关系。

栗如:
生活中许多的东西都具有多种款式,也就是,在多个维度上有多个不同的值,如包包;
这样,如果要将这些各种款式的包都实现,不采用桥接模式的话,就需要通过不断地继承;不仅耦合度大,而且类的数量也会爆炸膨胀;

如果使用桥接模式,那么,就可以将不同的维度的实现分离开来,再利用组合关系组合到抽象类中;
如,将颜色接口分离,它可以有很多个实现类红色、蓝色、黄色…将皮质接口分离,它可以有实现类鳄鱼皮、塑料…
这样,不管抽象类想怎么组合这些维度,把包包做出什么款式,都只需要各自传入相应的实现类即可。

角色:
抽象化角色;
扩展抽象化角色;
实现化角色;
具体实现化角色;
访问者

实现:
将不同维度的实现分离为实现化角色,根据需要为其添加具体实现类;
再将需要的具体实现类组合到抽象化角色中,成为对象的一部分。

优点:
抽象与实现分离,扩展能力强,符合开闭原则;
降低耦合度;
符合合成复用原则。

缺点:
关联关系都建立在抽象层,增加系统开发难度。

桥接模式结构图,图源于文章首部链接
扩展:
桥接模式可以跟适配器模式配合使用,如当具体实现化角色的功能模块已存在,但接口不匹配时。
其实适配器模式的应用应该是较广的。

8、装饰者模式(Decorator)

在保持原有对象不变的情况下,动态地给他添加一些职责。
栗如房子装修。

个人认为,装饰者模式的侧重点在于装饰,即,每个角色都实现了抽象构建者,只不过抽象装饰者是利用了已有的具体构建者来实现,并且还给其动态添加了职能;
而桥接模式侧重点在于组合,即,只有扩展抽象化角色实现了抽象化角色,其它的具体实现类只是组成成为抽象化角色一部分而已。它们无法独立成为一个扩展抽象化对象。

角色:
抽象构建角色
具体构建角色
抽象装饰角色
具体装饰角色
访问者

实现:
具体构建者已经实现类抽象构建角色接口,能独立成为一个对象;
抽象装饰者也实现了抽象构建者,它通过组合已有具体构建者的方式来实现,并为其添加需要的职能。

优点:
符合合成复用原则,取代了继承的方式,降低了耦合度,更加灵活;

装饰者模式结构图,图源于文章首部链接

扩展:
当只有一个具体构建者时,可以去掉抽象构建角色;
当只有一个具体装饰者时,可以去掉抽象装饰角色。

9、外观模式(Facade)

为一个具有多个复杂子系统的系统提供一个一致的外界访问的接口,使其更易访问。

栗如,日常生活中我们去某个地方办事,可能要寻找多个窗口(医院的缴费、拿药就是不同窗口);如果可以提供一个综合性窗口,为我们提供所需要的各种服务,那就会方便很多,而且我们也不需要了解这些窗口到底在哪里了。

角色:
外观角色,
子系统角色,
访问者

优点:
符合迪米特法则,降低了客户与系统之间的耦合度;
客户使用更加方便。

缺点:
当系统添加新的子系统时,可能要修改系统或客户端代码,不符合开闭原则;
无法很好地限制客户对子系统的访问。

外观模式结构图,图源于文章首部链接
扩展:
关于当添加子系统时不符合开闭原则的问题,可以在系统中添加一个抽象外观角色,可以在一定程度上缓解。

10、代理模式(Proxy)

在一些情况下,某些对象不想或者不能直接去访问另一个对象,这时候就需要一个“代理”。
栗如,通过安全代理来对用户的访问权限进行控制,即代理会在用户访问真实主题前对用户权限进行检查。

角色:
抽象主题
真实主题
代理
访问者

实现:
代理中封装了对真实主题的访问以及一些前置/后置操作,
用户通过代理,来对真实主题进行访问。

优点:
降低了耦合度。
可以扩展一些功能。

缺点:
降低了系统的响应速度,
提高了系统的复杂度。

代理模式结构图,图源于文章首部链接

扩展:
在代理模式中,代理跟真实主题是一一对应的;当真实主题越多,代理就越多。
因此,可以使用动态代理的方式:
只需要一个代理,使用时再选择某个真实主题,然后利用反射,来执行目标方法。(SpringAOP)

动态代理模式结构图,图源于文章首部链接

11、享元模式(Flyweight)

运用共享技术来实现对大量细粒度对象的复用。

生活中,有很多东西都是相似而又有一点不一样的,栗如,五子棋的黑白棋子、地上的对称瓷砖…
如果使用时对每一个都创建新对象,将消耗大量系统资源。

因此,我们可以把这些对象划分为:
1)内部状态:不随环境改变的可共享部分
2)外部状态:随环境改变的不可共享部分
然后,
对内部状态进行共用,将外部状态进行外部化:即,将其作为参数传入方法中。

栗如,五子棋的黑棋子、白棋子,它们的颜色、大小分别都是固定的,但位置不一定;
因此,我们就可以使用工厂对象,实现对黑棋、白棋对象的共享,而将非共享部分的Point位置对象作为参数传入。

角色:
抽象享元角色
具体享元角色
享元工厂角色
非享元角色
访问者

实现:
抽象享元角色定义接口规范具体享元角色要实现的方法,
客户可以提供享元工厂获得某个具体享元角色(同类型的都是获得同一个实例),
客户将非享元角色作为参数传入具体享元角色中,实现其不一样的地方。

优点:
当系统中有大量相似/相同对象时,大大减少了系统中对象的数量;

缺点:
系统的非享元角色需要外部化,增加程序的复杂性;
加载外部化部分需要一点时间。

享元模式结构图,图源于文章首部链接
扩展:
其实享元模式分为:
1)单纯享元模式:顾名思义,就是没有外部状态,不需要外部化
2)复合享元模式

12、组合模式(Composite)

也叫部分——整体模式。将对象组合成树状的层次结构,使用户对单个对象和组合对象具有一致的访问接口。

在生活中,有很多具有部分——整体关系的事物,又有一些相同的功能,如文件和文件夹,窗体的容器控件和普通控件…如果用组合模式来进行处理,会很方便。

角色:
抽象构件
树枝构件
树叶构件
访问者

组合模式又分为
1)透明式:对构件的管理方法写在抽象构建角色中,树枝、树叶都要实现,尽管树叶并没有这些方法。这会造成一些安全问题,但可以使客户端无需区别树枝、树叶来进行访问,对他们来说是透明的;
2)安全式:对构件的管理方法只在树枝中,树叶不用实现,客户端要区别树枝跟树叶。

实现(安全式):
定义抽象构件接口,规范构件都要实现的方法;
树叶构件实现抽象构件的方法,树枝构件除了要实现抽象构件的方法,还要另外定义对子构件的管理方法(增加、删除…);

优点:
当在组合体中加入新对象,不用修改源代码,符合开闭原则;

缺点:
客户端需要知道类之间的层次关系;
不容易限制容器中的构件;
不容易用继承来扩展构件功能。

组合结构图,图源于文章首部链接

五 、行为型设计模式

13、模板方法模式(Template Method)

定义好一个操作的基本骨架,把一些步骤延长到子类中实现。

生活中,有时我们去办事,往往需要进行一系列的动作。栗如去医院,就要挂号、看医生、缴费、拿药,这些顺序是一定的。对于每个病人,挂号、缴费、拿药都是一样的事情,但是看医生的过程是不一样的,那么这个就可以延长到子类中去实现。

角色:
抽象类
具体子类
访问者

其中,抽象类中包括了模板方法和基本方法,
模板方法:包含了操作需要的一系列方法,以确定执行顺序的方法(骨架);
基本方法:可以有具体方法、抽象方法(要被延长)、钩子方法(子类选择性实现)。

实现:
定义抽象类,规范该操作要执行的所有方法,包括模板方法和基本方法;
使用具体子类,实现抽象类中未实现的方法。

优点:
将子类的共有部分都封装在抽象类中,提高了代码的复用性;
子类可以继续扩展。

缺点:
当实现类很多的时候,会产生很多子类;
子类的实现会影响父类,反向控制。(抽象类的通病吧?)

模板方法模式结构图,图源于文章首部链接

扩展:
子类可以通过钩子方法对父类进行控制(逻辑判断,写在模板方法中)

扩展模板方法模式结构图,图源于文章首部链接

14、解释器模式(Interpreter)

如果某些问题重复出现,且这些问题可以归纳为符合某种“文法”表达式的语言,就通过可以规定文法的规则,来对这些问题进行“解释”。

文法:
例如,当我们规定 韶光或者广州的老人、妇女、儿童三类人群都可以免费搭车,就可以归纳出以下的文法规则:
图源于文章首部链接
那么,符合文法的语言,就有例如:(1)“韶关的老人”、(2)“深圳的儿童”、(3)“广州的妇女”…
而其中,符合文法规则的就有(1)、(3),可解释为“免费搭车”;不符合规则的有(2),可解释为“收费搭车”。

角色:
抽象表达式
终结符表达式
非终结符表达式
环境
访问者

实现:
抽象表达式角色定义了解释器的接口,终结符、非终结符表达式角色都实现了该接口;
其中,终结符表达式角色主要工作是对语句中某一个节点的解释:保存传入的某一个节点的规则,在解释器方法中对传入的字符进行解释(即检验是否符合规则——也就是该字符能否在规则中找到,然后做出反应);
非终结符表达式角色的主要工作是对整个语句的分隔和解释:根据需要聚合多个抽象表达式角色(指向终结符表达式对象的),在其解释器方法中,先将语句切割为多个节点;再利用具体传入的每个终结符表达式对象来对每个节点进行解释;
环境角色是保存规则(细化到以每个节点为单位),聚合多个终结符表达式角色和非终结符表达式角色,给终结符表达式角色提供规则数据,然后提供一个接口(调用非终结符表达式角色的解释器方法)给用户去解释语句

优点:
可以通过继承非终结符表达式角色的方式,对文法规则进行扩展;
语句的每个节点都是类似的,容易实现。

缺点:
当文法规则很多时,扩展时类的数量会发生爆炸性增长;
效率低,使用该模式时通常会有大量的循环或递归调用;
应用场景少。

解释器模式结构图,图源于文章首部链接
扩展:
其实当要分析一些数学表达式时,已经有很多现有库可以使用了,例如Expression4J、Jep等…

举个栗子:

图源于文章首部链接

15、策略模式(Strategy)

当一个问题有多种解决方案时,可以使用策略模式,把这些方案封装起来,并可以相互替换,用户每次可以按需使用不同策略。

角色:
抽象策略类
具体策略类
环境类
访问者

实现:
首先在抽象策略类中定义一个统一的方法接口;
各具体策略类按照不同的方式去实现接口;
在环境类中,利用聚合关系传入一个具体策略类;提供一个接口给客户执行需要的方法(调用传入的具体策略类的方法)。

优点:
避免了多重条件判断,提高可维护性;
把算法的实现与使用相分离;
用户可以添加具体策略类来进行扩展。符合开闭原则。

缺点:
客户端要对策略类了解,以选择合适的算法;
可能会导致策略类很多。

策略模式结构图,图源于文章首部链接

扩展:
当策略类很多时,可以将环境类升级为策略工厂类;在里面使用一个map来存放具体策略对象,给出get / set 方法让用户可以按需取出 / 放入具体策略对象,并进行方法调用。

扩展策略模式结构图,图源于文章首部链接

16、命令模式(Command)

将“请求者” 与“实现者” ,两者之间通过命令对象进行沟通,便于对命令对象进行存储、管理…
栗如生活中得遥控器…

角色:
抽象命令
具体命令
请求者 / 调用者
接收者/ 实现者

实现:
抽象命令角色是定义执行方法的接口;
各个具体命令角色按需实现接口的方法(调用接收者);
请求者通过调用具体命令角色来执行请求;
接收者执行真正的处理请求操作。

优点:
将请求者与实现者解耦,降低耦合度。
添加、删除命令方便,符合开闭原则。
与组合模式共用,可以实现宏命令等(调用者为树枝、具体命令为树叶)。
与备忘录模式共用,可以实现操作的撤销、删除。

缺点:
当命令增多时,系统的复杂度会增加。

命令模式结构图,图源于文章首部链接

扩展:
组合模式+命令模式:

扩展命令模式结构图,图源于文章首部链接

17、责任链/ 职责链模式(Chain of Responsibility)

在生活中,有时候一个请求可以被多个对象处理,但每个对象的处理条件或权限不同,因此,用户需要确切知道所有可接受请求的对象的关系。栗如请假。
为了减少请求者与接收者之间的耦合度,将各个接受者按照一定的顺序,以前一个记住下一个的方式给链接起来,形成一条处理请求的责任链。

角色:
抽象处理者
具体处理者
访问者

实现:
使用链表数据结构。
抽象处理者定义处理请求的抽象方法,以及一个后继链接;
具体处理者实现抽象处理者定义的抽象方法;
访问者先将各具体处理者按照需要“链接”起来,然后就可以给第一个处理者发送请求了。

优点:
降低了请求者跟处理者的耦合度;
可以根据需要添加处理者,符合开闭原则;
可以灵活调整处理者的处理顺序;
每个处理者只需要处理自己的那一部分,符合单一职责原则;
处理者只需持有一个后继对象的引用,省了很多if语句;

缺点:
没有确切的处理者,请求最后不一定能被处理;
责任链较长时,请求可以要通过多个处理者对象,降低效率;
责任链由客户端来连接,提高了客户端的复杂性。

责任链模式结构图,图源于文章首部链接
责任链,图源于文章首部链接
扩展:
具体处理者可以只处理一部分请求,然后交给下个处理者。

18、状态模式(State)

有的对象拥有“状态”,当内部状态改变时,行为也会改变。可以使用状态模式,来代替一系列的if-else 判断逻辑。

角色:
环境
抽象状态
具体状态
访问者
(个人觉得状态模式跟策略模式的结构很像)

实现:
环境角色聚合了抽象状态角色,用户可以通过环境角色来改变状态、执行行为;
抽象状态角色定义了行为方法;
具体状态角色实现了行为方法。同时,也可以通过组合一个环境角色来改变状态。

优点:
把每种状态独立为一个对象,降低了耦合度,符合单一职责原则;
便于状态的扩展;

缺点:
增加了类、对象的个数,提高了 系统复杂度。

状态模式结构图,图源于文章首部链接扩展:
可以与享元模式结合,即在环境角色中定义一个集合来持有状态对象,每次只需要从集合里获取需要的状态对象。

扩展状态模式结构图,图源于文章首部链接

19、观察者模式(Observer)

当一个对象的行为发生改变时,会影响多个其它对象,就可以使用观察者模式。
也叫发布——订阅模式、模型——视图模式。

角色:
抽象主题
具体主题
抽象观察者
具体观察者
访问者

实现:
在抽象主题中实现添加、删除观察者,以及通知观察者的抽象方法;
具体主题实现通知观察者的方法;
抽象观察者定义观察者做出响应的抽象方法;
具体观察者实现做出响应的方法。

优点:
降低了事件与观察者之间的耦合度,成为抽象耦合关系;

缺点:
观察者很多时,通知费时。

观察者模式结构图,图源于文章首部链接

扩展:
在Java中,实现 java.util.Observable 类和 java.util.Observer 接口就可以实现观察者模式。
其中,前者为抽象主题角色,后者为抽象观察者角色。
扩展观察者模式结构图,图源于文章首部链接

20、中介者模式(Mediator)

在生活中,存在很多复杂的网状的交互关系。栗如,群聊。
这时候,每个人都要记住所有好友;如果用“中介者”来实现,就能降低耦合度,符合迪米特法则。

个人认为,与观察者模式相比,观察者模式侧重于作出反应,而中介者模式侧重于交流。

角色:
抽象中介者
具体中介者
抽象同事类
具体同事类

实现:
抽象中介者提供了注册和转发消息的抽象接口;
具体中介者实现接口,并持有一个“同事列表”(聚合);
抽象同事类持有一个抽象中介者的引用(聚合),实现了设置中介者的方法,定义了发送和接收消息的抽象接口;
具体同事类实现了发送和接收消息的接口。

优点:
降低了同事间的耦合度,符合迪米特法则;
将对象的一对多关联变为一对一关联,便于扩展。

缺点:
同事太多时中介者的责任重大,提高了系统复杂度。

中介者模式结构图,图源于文章首部链接

扩展:
可以将抽象中介者去掉,并且将具体中介者设为单例;
同事类不持有中介者的引用,而是在需要的时候直接获取一个中介者单例。

扩展中介者模式结构图,图源于文章首部链接

21、迭代器模式(Iterator)

在日常中,我们经常要遍历某个聚合对象的所有元素。栗如链表。我们经常把其创建与遍历写在一个类中,这样,当我们要改变遍历的方式,就要修改源代码,不符合开闭原则。因此,我们可以使用一个“迭代器”,将对对象的遍历给抽取出来。

角色:
抽象聚合类
具体聚合类
抽象迭代器
具体迭代器

实现:
抽象聚合类定义了添加、删除元素、获取迭代器的方法;
具体聚合类实现了方法,且持有一个元素列表;
抽象迭代器定义了遍历元素的方法;
具体迭代器实现了方法,且也持有一个元素列表(可从构造方法中传入)。

优点:
将遍历元素分离出来,简化了聚合类;
便于聚合类与迭代器的扩展;
封装性好,为不同的聚合类提供了统一的接口。

缺点:
提高了系统复杂性。

迭代器模式结构图,图源于文章首部链接

扩展:
可以与组合模式结合,将抽象聚合类作为组件接口,将具体聚合类拆分为树枝、树叶,可以对树枝进行迭代。

扩展迭代器模式结构图,图源于文章首部链接

22、访问者模式(Vistor)

生活中,有些对象存在多种元素,且不同的其它对象访问这些元素时,又是执行不同的操作,这时候就可以用访问者模式。
栗如,对于材料集,包含纸、铜…等等,对于不同的公司如造币公司跟艺术公司,对于各个元素的操作就是不同的,造币公司将纸造成纸币,而艺术公司会做成图画…

角色:
抽象元素
具体元素
抽象访问者
具体访问者
对象结构

实现:
抽象元素角色定义了一个接受访问者(即作为参数传入)的抽象方法;
具体元素角色实现了该方法,(一般是visitor.visit(this) );
抽象访问角色定义了访问者访问元素的抽象方法;
具体访问角色实现了抽象访问者中的方法;
对象结构角色使用聚合对象来保存元素,提供了让访问者对所有元素进行遍历访问的方法。

优点:
将数据结构与操作解耦,使得操作可以相对自由地变化;
将每个访问者的行为封装到一个类,符合单一职责原则;
便于对元素的操作进行扩展;
可以通过访问者来实现整个系统通用的功能,提高复用性。

缺点:
当增加元素时,访问者需要做出改变,不符合开闭原则;
具体元素对访问者公布细节,破坏封装;
依赖了具体类而不是抽象类,破坏了依赖倒置原则。

访问者模式结构图,图源于文章首部链接

扩展:
1、与迭代器模式并用,即,在对象结构角色中利用迭代器进行遍历;
2、与组合模式并用,栗如,当元素角色有组合关系是,可以划分为树枝树叶。

包含组合模式的访问者模式结构图,图源于文章首部链接

23、备忘录模式(Memento)

在日常的数据处理中,我们可能会“后悔“一些事情,对于应用程序,我们往往可以使用一些快捷键来”撤销”操作,这就可以用备忘录模式来解决。

角色:
发起人
备忘录
管理者
访问者

实现:
发起人可以设置/ 获取状态,也可以进行备忘录的创建和恢复;
备忘录用于存储状态信息;
管理者可以对备忘录进行保存和获取(发起人创建后可以进行保存,发起人要恢复时可以进行获取),但不能修改;
访问者通过对发起人和管理者的访问来执行操作。

优点:
便于恢复状态;
只有发起人可以对状态进行访问,实现了封装性 ;
发起人不对备忘录进行管理,而是由管理者 ,符合单一职责原则。

缺点:
当状态较多时 ,资源消耗较大 。

备忘录模式结构图,图源于文章首部链接

扩展:
可以与原型模式结合使用,利用其克隆特性,代替备忘录角色。

扩展备忘录模式结构图,图源于文章首部链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值