欢迎访问『www.cnblogs.com/blog-ice』

# 设计模式

参考网址:http://c.biancheng.net/view/1371.html


依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类
双向的关联可以用带两个箭头或者没有箭头的实线来表示,单向的关联用带一个箭头的实线来表示,箭头从使用类指向被关联的类
聚合关系可以用带空心菱形的实线来表示,菱形指向整体
组合关系用带实心菱形的实线来表示,菱形指向整体
泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类
实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口

## 23种设计模式

--------------------------------------------------------------------------------
创建型模式 5
--------------------------------------------------------------------------------
1.单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
2.原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
3.工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
4.抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
5.建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

1.单例模式-Singleton 某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
4种线程安全方法
1.静态内部类
private static class InnerClass{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return InnerClass.INSTANCE;
}
2.饿汉式(静态常量直接new,或静态代码块直接new)
3.懒汉式(双重检查,synchronized(Singleton.class)前后检查)
4.枚举(防止反序列化)
public enum Singleton{
INSTANCE;
}

单例模式的优点:
1.单例模式可以保证内存里只有一个实例,减少了内存的开销。
2.可以避免对资源的多重占用。
3.单例模式设置全局访问点,可以优化和共享资源的访问。

单例模式的缺点:
1.单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
2.在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
3.单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

单例模式的使用场景:频繁,耗资源,工具类,访问数据库/文件
• 需要频繁的进行创建和销毁的对象;
• 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
• 工具类对象;
• 频繁访问数据库或文件的对象


2.原型模式-Prototype (孙悟空72变)将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
浅层复制:实现Cloneable(标记)并调用Object.clone()方法
深层复制:ObjectXXXputStream,ByteArrayXXXputStream,Serializable

原型模式的优点:
1.Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
2.可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
原型模式的缺点:
1.需要为每一个类都配置一个 clone 方法
2.clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
3.当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

原型模式的应用场景:相同或相似,成本大,繁琐,改属性
1.对象之间相同或相似,即只是个别的几个属性不同的时候。
2.创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
3.创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
4.系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。

在 Spring 中,原型模式应用的非常广泛,例如 scope='prototype'、JSON.parseObject() 等都是原型模式的具体应用。

原型模式的扩展
增加一个Map统一管理原型


3.工厂模式-Factory Method(动物农场)定义一个用于创建产品的接口,由子类决定生产什么产品。
1.简单工厂
一个工厂,根据产品type生产不同产品

优点:
1.工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
2.客户端无需知道所创建具体产品的类名,只需知道参数即可。
3.也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
缺点:
1.简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
2.使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
3.系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
4.简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。

2.工厂方法
一个抽象工厂,
多个具体工厂(每个工厂生产一种产品--动物),
一个抽象产品,
多个具体产品

优点:
1.用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
2.灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
3.典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
缺点:
1.类的个数容易过多,增加复杂度
2.增加了系统的抽象性和理解难度
3.抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。

注意:当需要生成的产品不多且不会增加,一个具体工厂类就可以完成任务时,可删除抽象工厂类。这时工厂方法模式将退化到简单工厂模式。

4.抽象工厂-Abstract Factory(动植物农场)提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
一个抽象工厂,
多个具体工厂(每个工厂生产多种产品--动物/植物)
多个抽象产品,
多个具体产品

抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下。
1.可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
2.当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
3.抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。

其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。

抽象工厂模式通常适用于以下场景:
1)当需要创建的对象是一系列相互关联或相互依赖的[产品族]时,如电器工厂中的电视机、洗衣机、空调等。
2)系统中有多个产品族,但每次只使用其中的[某一族产品]。如有人只喜欢穿某一个品牌的衣服和鞋。
3)系统中提供了产品的类库,且[所有产品的接口相同],客户端不依赖产品实例的创建细节和内部结构

抽象工厂--->退化(一个等级的产品)--->工厂方法--->退化()-->成简单工厂
模式的扩展
1.抽象工厂模式的扩展有一定的“开闭原则”倾斜性:
2.当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
3.当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。

另一方面,当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。

5.建造者-Builder (装修)将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示
产品(展示)、 定义复杂对象的部件,定义展示特性 ------------>构造与表示分离
抽象建造者、 定义子部件创建方法 createPart()...,定义返回产品方法 getResult()
具体建造者(创建部件)、 实现创建部件方法 -------------->生成部件
指挥者(组装) 一个 construct()方法 调用建造者组装产品,并调用建造者的getResult()反回结果 --------------->构造对象

建造者模式主要适用于以下应用场景:部件多,顺序,参数多
1.相同的方法,不同的执行顺序,产生不同的结果。
2.多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
3.产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
4.初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。

建造者模式和工厂模式的区别
通过前面的学习,我们已经了解了建造者模式,那么它和工厂模式有什么区别呢?
1.建造者模式更加注重[方法的调用顺序],工厂模式注重[创建对象]。
2.创建对象的力度不同,建造者模式创建[复杂的对象],由各种复杂的部件组成,工厂模式创建出来的[对象都一样]
3.关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,[还要知道对象由哪些部件组成]。
4.建造者模式根据建造过程中的[顺序]不一样,最终对象部件组成也不一样。
模式的扩展
建造者(Builder)模式在应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色
--------------------------------------------------------------------------------
结构型模式 7
--------------------------------------------------------------------------------
6.代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
7.适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
8.桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
9.装饰(Decorator)模式:在[不改变]现有对象结构的情况下,[动态]地给对象[增加]一些职责,即增加其[额外]的功能。
10.外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
11.享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
12.组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

以上 7 种结构型模式,除了适配器模式分为类结构型模式和对象结构型模式两种,其他的全部属于对象结构型模式

6.代理(Proxy)模式
抽象主题(Subject)类: 定义业务方法
真实主题(Real Subject)类: 实现业务方法
代理(Proxy)类: 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理模式的主要优点有:
1.代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
2.代理对象可以扩展目标对象的功能;
3.代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

其主要缺点是:
1.代理模式会造成系统设计中类的数量增加
2.在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
3.增加了系统的复杂度;

1.静态:代理类持有代理对象,在执行方法前后增加额外功能(抽象主题,真实主题,代理)
2.动态:程序运行中,用反射动态创建
jdk: InvocationHandler, Proxy.newProxyInstance(classloader, interface, invocationHandler)(只能代理接口,因为代理类已经继承了Proxy,不能再继承实现类)
cglib: MethodInterceptor,Enhancer(可以代理接口和实现类)

面分析了代理模式的结构与特点,现在来分析以下的应用场景。---->隐藏,替换,控制,附加,延迟
1.远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
2.虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
3.安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
4.智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
5.延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。

7.适配器(Adapter)模式 接口
目标(Target)接口
适配者(Adaptee)类
适配器(Adapter)类 target(adaptee())

类结构型:Adapter extends Adaptee implements Target, 在实现方法中调用继承适配者的方法 target(adaptee())
对象结构型: Adapter DI Adaptee implements Target 在实现方法中调用适配者的方法 target(adaptee())

适配器模式(Adapter)通常适用于以下场景: 接口不一致
1.以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
2.使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。

8.桥接(Bridge)模式 抽象-实现 两个维度独立变化
抽象化(Abstraction)角色, 持有[实现化实例],定义接口 operation()
扩展抽象化(Refined Abstraction)角色, 实现接口 operation() 调用实现化实例方法 operationImpl()
实现化(Implementor)角色, 定义接口 operationImpl()
具体实现化(Concrete Implementor)角色 实现接口 operationImpl()

抽象化角色是关键,抽象类,并包含一个对实现化对象的引用

桥接(Bridge)模式的优点是:
1.抽象与实现分离,扩展能力强
2.符合开闭原则
3.符合合成复用原则
4.其实现细节对客户透明

缺点是:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。

桥接模式通常适用于以下场景:两个维度独立变化,替换继承,灵活性
1.当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
2.当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
3.当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

实现化角色的接口与适配器结合使用

9.装饰(Decorator)模式 不改变,动态,增加,额外
抽象构件(Component)角色, 定义接口 operation()
具体构件(ConcreteComponent)角色, 实现方法 operation()
抽象装饰(Decorator)角色, 持有[构件实例],实现[抽象构件],实现方法中调用构件实例的方法operation()
具体装饰(ConcreteDecorator)角色 实现方法中怎加额外功能

抽象装饰角色是关键,[继承]抽象构件,包含具体构件实例,扩展抽象构件功能

装饰器模式的主要优点有:
1.装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
2.通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果
3.装饰器模式完全遵守开闭原则

其主要缺点是:装饰器模式会增加许多子类,过度使用会增加程序得复杂性。

适用的应用场景:
1.当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
2.当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
3.当对象的功能要求可以动态地添加,也可以再动态地撤销时。

10.外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
外观(Facade)角色, 提供一个共同的对外方法(调用所有子系统方法)
子系统(Sub System)角色,
客户(Client)角色 调用外观的方法

外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。
1.降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
2.对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
3.降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

外观(Facade)模式的主要缺点如下。
1.不能很好地限制客户使用子系统类,很容易带来未知风险。
2.增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

使用外观模式应用场景:分层,子系统很多,分离
1.对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
2.当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
3.当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

在外观模式中,当增加或移除子系统时需要修改外观类,这违背了“开闭原则”。如果引入抽象外观类,则在一定程度上解决了该问题


11.享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
抽象享元角色(Flyweight), 定义接口operation(非享元对象)
具体享元(Concrete Flyweight)角色, 实现接口operation(非享元对象)
非享元(Unsharable Flyweight)角色, 作为operation的参数注入到享元对象
享元工厂(Flyweight Factory)角色 Map,创建和管理享元

享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

其主要缺点是:
1.为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
2.读取享元模式的外部状态会使得运行时间稍微变长。

享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式:大量,分组,外部化,实例多
1.系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
2.大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
3.由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。

享元模式的扩展:单纯享元模式(不存在非享元角色);复合享元模式(list add remove)

12.组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
抽象构件(Component)角色, 定义 operation()
树叶构件(Leaf)角色, 实现 operation()
树枝构件(Composite)角色 / 中间构件 集合,增,删,获取子, operation()方法中循环调用子构件的operation()
(1) 透明方式:抽象构件声明了所有子类中的全部方法
(2) 安全方式:将管理子构件的方法移到[树枝]构件中

组合模式的主要优点有:
1.组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
2.更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

其主要缺点是:
1.设计较复杂,客户端需要花更多时间理清类之间的层次关系;
2.不容易限制容器中的构件;
3.不容易用继承的方法来增加构件的新功能;

适用的以下应用场景:整体与部分
1.在需要表示一个对象整体与部分的层次结构的场合。
2.要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。

--------------------------------------------------------------------------------
行为型模式概述 11
--------------------------------------------------------------------------------
13.模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。类行为型模式
14.策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。对象行为模式
15.命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。对象行为模式
16.职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。对象行为模式
17.状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。对象行为模式
18.观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。对象行为模式
19.中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。对象行为模式
20.迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。对象行为模式
21.访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。对象行为模式
22.备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。对象行为模式
23.解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。类行为型模式

以上 11 种行为型模式,除了[模板方法模式]和[解释器模式]是类行为型模式,其他的全部属于对象行为型模式

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

该模式的主要优点如下:
1.它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
2.它在父类中提取了公共的部分代码,便于代码复用。
3.部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

该模式的主要缺点如下。
1.对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
2.父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
3.由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

模板方法模式通常适用于以下场景:变与不变
1.算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
2.当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
3.当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

14.策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
抽象策略(Strategy)类-------- 定义策略方法(接口或抽象)
具体策略(Concrete Strategy)类------ 实现策略方法
环境(Context)类----- 持有当前策略,公开策略方法(调用当前策略方法),切换/获取策略

相互替换

策略模式的主要优点如下。
1.多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if...else 语句、switch...case 语句。
2.策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
3.策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
4.策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
5.策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

其主要缺点如下:理解,多
1.客户端必须理解所有策略[算法的区别],以便适时选择恰当的算法类。
2.策略模式造成很多的策略类,增加维护难度。

策略模式在很多地方用到,如 Java SE 中的容器布局管理就是一个典型的实例,Java SE 中的每个容器都存在多种布局供用户选择。在程序设计中,通常在以下几种情况中使用策略模式较多。
1.一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
2.一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
3.系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
4.系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
5.多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

策略模式的扩展
在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度

15.命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
抽象命令类(Command)角色, execute()
具体命令类(Concrete Command)角色, Receiver实例,实现execute()并调用receiver.action()方法执行处理逻辑
实现者/接收者(Receiver)角色, 处理逻辑action()
调用者/请求者(Invoker)角色 Command实例,call()调用command.execute()

Invoker--->Command--->Receiver
call() execute() action()

命令模式的主要优点如下。
1.通过引入中间件(抽象接口)降低系统的耦合度。
2.扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
3.可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
4.方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
5.可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。

其缺点是:
可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。

命令模式通常适用于以下场景:解耦,增加/删除,宏命令,Undo/Redo
1.请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
2.系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能。
3.当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
4.当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。


16.职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
抽象处理者(Handler)角色, next-->自己, 定义处理方法
具体处理者(Concrete Handler)角色, 实现处理方法(并将请求传递给next)
客户类(Client)角色 组装责任链,提交请求

抽象处理者(Handler)角色是关键, 定义一个处理请求的接口,包含抽象处理方法和一个后继连接next

其主要优点如下:耦合度,扩展性,灵活性,简化,分担
1.降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
2.增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
3.增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
4.责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
5.责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

其主要缺点如下:不保证,性能,复杂,循环调用
1.不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
2.对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
3.职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。


责任链模式通常在以下几种情况使用:运行时,动态,不明确
1.多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
2.可动态指定一组对象处理请求,或添加新的处理者。
3.需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。

职责链模式存在以下两种情况。
1.纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
2.不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。

17.状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
环境类(Context)角色---- 持有当前状态,切换当前状态,定义客户端需要的接口(调用状态的处理接口 state.handler(this))
抽象状态(State)角色----- 定义状态对应行为的处理接口 handle(Context)
具体状态(Concrete State)角色------ 实现处理接口(----->根据需要调用context的切换状态方法)

优点如下:
1.结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
2.将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
3.状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

缺点如下。
1.状态模式的使用必然会增加系统的类与对象的个数。
2.状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
3.状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

通常在以下情况下可以考虑使用状态模式:状态,分支
1.当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
2.一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

状态模式与责任链模式的区别
状态模式和责任链模式都能消除 if-else 分支过多的问题。但在某些情况下,状态模式中的状态可以理解为责任,那么在这种情况下,两种模式都可以使用。
从定义来看,状态模式强调的是一个对象[内在]状态的改变,而责任链模式强调的是[外部]节点对象间的改变。
从代码实现上来看,两者最大的区别就是状态模式的各个状态对象[知道]自己要进入的下一个状态对象,而责任链模式并[不清楚]其下一个节点处理对象,因为链式组装由客户端负责。

状态模式与策略模式的区别
状态模式和策略模式的 UML 类图架构几乎完全一样,但两者的应用场景是不一样的。策略模式的多种算法行为择[其一]都能满足,彼此之间是[独立]的,
用户可自行更换策略算法,而状态模式的各个状态间存在[相互关系],彼此之间在一定条件下存在[自动切换]状态的效果,并且用户无法指定状态,只能设置[初始]状态。


18.观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
抽象主题(Subject)角色: 集合及增删方法,定义通知方法(抽象方法)
具体主题(Concrete Subject)角色: 实现通知方法,通知所有观察者(调用观察者方法)
抽象观察者(Observer)角色: 定义通知时被调用的方法。
具体观察者(Concrete Observer)角色: 实现被调用方法。

观察者模式是一种对象行为型模式,其主要优点如下。
1.降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
2.目标与观察者之间建立了一套触发机制。

它的主要缺点如下。
1.目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
2.当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

应用场景: 一对多,独自改变和复用,广播,多层级嵌套
1.对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
2.当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
3.实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
4.多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。

1. Observable类
Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。
void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。
2. Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。

19.中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
抽象中介者(Mediator)角色: 定义一个注册,转发
具体中介者(Concrete Mediator)角色: 集合(同事),实现注册和转发。
抽象同事类(Colleague)角色: 中介引用,定义接受和发送接口。
具体同事类(Concrete Colleague)角色: 实现接受,发送(调用中介的转发)。

中介者模式是一种对象行为型模式,其主要优点如下:
1.类之间各司其职,符合迪米特法则。
2.降低了对象之间的耦合性,使得对象易于独立地被复用。
3.将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

应用场景:网状结构
1.当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
2.当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

模式的扩展
在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单。
不定义中介者接口,把具体中介者对象实现成为单例。
同事对象不持有中介者,而是在需要的时候直接获取中介者对象并调用。

20.迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

应用场景:遍历
1.当需要为聚合对象提供多种遍历方式时。
2.当需要为遍历不同的聚合结构提供一个统一的接口时。
3.当访问一个聚合对象的内容而无须暴露其内部细节的表示时。


21.访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
抽象访问者(Visitor)角色: [定义多个接口visit(具体元素),一个具体元素对应一个]
具体访问者(ConcreteVisitor)角色: [实现visit(具体元素)方法,可调用操作元素operateElement()]
抽象元素(Element)角色: [定义一个接口accept(Visitor) 的接口]
具体元素(ConcreteElement)角色: [实现accept(Visitor),其方法体通常都是 visitor.visit(this)] ,[操作元素operateElement()]
对象结构(Object Structure)角色: [元素集合及操作]

访问者(Visitor)模式是一种对象行为型模式,其主要优点如下:
1.扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
2.复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
3.灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
4.符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

访问者(Visitor)模式的主要缺点如下:
1.增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
2.破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
3.违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

应用场景:结构稳定-算法变化
1.对象结构相对稳定,但其操作算法经常变化的程序。
2.对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
3.对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作

模式的扩展
访问者(Visitor)模式是[使用频率较高]的一种设计模式,它常常同以下两种设计模式联用。
(1)与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。如【例1】中的对象结构是用 List 实现的,
它通过 List 对象的 Iterator() 方法获取迭代器。如果对象结构中的聚合类没有提供迭代器,也可以用迭代器模式自定义一个。
(2)访问者(Visitor)模式同“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式


22.备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
发起人(Originator)角色: [当前内部状态],[创建备忘录],[恢复备忘录数据]
备忘录(Memento)角色: [存储内部状态]
管理者(Caretaker)角色: 备忘录的[保存],[获取]

优点如下: 恢复,内部状态的封装,单一职责
1.提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
2.实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
3.简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

其主要缺点是:[资源]消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

应用场景:
1.需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
2.需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。

模式的扩展
在前面介绍的备忘录模式中,有单状态备份的例子,也有多状态备份的例子。下面介绍备忘录模式如何同原型模式混合使用。在备忘录模式中,
通过定义“备忘录”来备份“发起人”的信息,而原型模式的 clone() 方法具有自备份功能,所以,如果让发起人实现 Cloneable 接口就有备份自己的功能,这时可以删除备忘录类

23.解释器(Interpreter)模式:公交车卡的读卡器程序 提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
抽象表达式(Abstract Expression)角色: 定义解释器的接口interpret()。
终结符表达式(Terminal Expression)角色: 实现interpret(),解释终结符
非终结符表达式(Nonterminal Expression)角色: 实现interpret(),解释规则(非终结符)。
环境(Context)角色: 共享的数据,执行解释器的interpret()。
客户端(Client): 主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

下面分析它的应用场景:[文法,效率,解释]
1.当语言的文法较为简单,且执行效率不是关键问题时。
2.当问题重复出现,且可以用一种简单的语言来进行表达时。
3.当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。

注意:解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。如果碰到对表达式的解释,在 Java 中可以用 Expression4J 或 Jep 等来设计。

模式的扩展
在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java 提供了以下强大的数学公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,
它们可以解释一些复杂的文法,功能强大,使用简单。

 

posted on 2021-09-22 08:38  仙路尽头谁为峰  阅读(68)  评论(0编辑  收藏  举报
这里是自由发挥的天堂