设计模式的艺术一书的阅读笔记

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码的可靠性。

UML

类图(Class Diagram) 是出现在系统中的不同类来描述系统的静态结构,主要用来描述不同的类以及它们之间的关系。

UML 属性的表示方式:

1
可见性(+:public、-:private、#:protected) 名称:类型[=默认值]

UML 方法的表示方式:

1
可见性 名称(参数列表)[: 返回类型]

类之间的关系

关联关系(Association):(一个对象作为另一个对象的成员变量)

双向关联、单向关联、自关联、多重关联、聚合关系(整体和部分关系,部分可独立存在)、组合关系(在类中实例化另一个对象,共存亡)

依赖关系(Dependency):(使用另一个类作为参数、将另一个类对象作为局部变量、调用另一个类的静态方法)

泛化关系(Generalization)又称继承关系:(描述父类和子类之间的关系)

接口与实现关系

面向对象设计原则

单一职责原则

单一职责原则(Single Responsibility Principle,SRP):一个类只负责一个功能领域中的相应职责。或者可以定义为:就一个类而言只有一个引起它变化的原因。

单一职责原则是实现高内聚、低耦合的指导方针,它是最简单又是最难运用的原则,需要设计人员发现类的不同的职责并将其分离。

开闭原则

开闭原则(Open-Closed Principle,OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

抽象化是开闭原则的关键。面向接口编程,在新需求到来时不需要改动抽象层而是添加具体的实现类即可完成。

里氏代换原则

里氏代换原则(Liskov Substitution Principle,LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。

在软件中将一个基类对象替换为它的子类对象,程序将不会产生任何错误和异常,在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。(多态的感觉),这样扩展功能可通过增加一个新的子类来实现。

依赖倒转原则

依赖倒转原则(Dependency Inversion Principle,DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程而不是实现编程。

接口隔离原则

接口隔离原则(Interface Segregation Principle,ISP):使用多个专门的接口,而不是使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

合成复用原则

合成复用原则(Composition Resuse Principle,CRP):尽量使用对象组合,而不是继承达到复用的目的

迪米特法则

迪米特法则(Law of Demeter,LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。

当一个模块发生修改时,尽量少得影响其他模块,扩展和重构更加容易,即限制软件实体间的通信。

创建型模式

单例模式

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Java 饿汉式,类加载时即实例化,占用系统资源
class EagerSingleton{
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton(){}
public static EagerSingleton getInstance(){
return instance;
}
}

// 双重检查锁定(Double-Check Locking),使用锁机制,性能有一定影响
class LazySingleton{
private volatile static LazySingleton instance = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(instance == null){
synchronized(LazySingleton.class){
if(instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}

// 静态内部类机制
class Singleton{
private Singleton(){}
private static class InnerClass{
private final static Singleton instance = new Singleton();
}
private static Singleton getInstance(){
return InnerClass.instance;
}
}

优点:

  1. 提供对唯一实例的受控访问。
  2. 系统中只存在一个对象,节约系统资源。
  3. 允许可变数目的实例(自行提供指定数目实例对象的类可称之为多例类)。

缺点:

  1. 单例模式没有抽象层,难以扩展。
  2. 单例类职责过重,将对象的创建和对象本身的功能耦合在一起。
  3. 对于自动垃圾回收,如果实例化共享对象长时间不被利用会被回收,下次使用又需要实例化,导致共享的单例对象状态丢失。

适用场景:

  1. 系统只需要一个实例对象。
  2. 客户调用类的单个实例只允许一个公共访问点。

简单工厂模式

简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态方法,因此简单工厂模式又称为静态工厂模式(Static Factory Method Pattern)。

  • Factory(工厂角色)
  • Product(抽象产品角色)
  • ConcreteProduct(具体产品角色)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 定义产品抽象
public interface Product{

}

// 定义具体的产品
public class ConcreteProductA implements Product{

}
public class ConcreteProductB implements Product{

}

// 定义工厂
class Factory{
public static Product getProduct(String arg){
Product product = null;
if("A".equals(arg)){
product = new ConcreteProductA();
}else if("B".equals(arg)){
product = new ConcreteProductB();
}

return product;
}
}

优点:

  1. 实现了对象创建和使用的分离,客户端只需要专注于使用产品
  2. 客户端无需知道所创建的具体产品的类名,而只需知道如何使用即可,减少了使用者的记忆量
  3. 引入配置文件可在不修改任何客户端代码的情况下进行更换和增加的新的具体产品类,在一定程度上提高了系统的灵活性

缺点:

  1. 工厂类集中了所有产品的创建逻辑,职责过重,一旦不能工作,整个系统都将受到影响
  2. 简单工厂模式会增加系统类的个数
  3. 系统扩展困难,增加新产品不得不修改工厂逻辑
  4. 简单工厂模式使用静态工厂方法,无法形成基于继承等级结构

适用场景:

  1. 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂
  2. 客户端只知道传入工厂类的参数,而不关系对象如何被创建

工厂方法模式

工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到子类。工厂方法模式又称工厂模式(Factory Pattern)

  • Product(抽象产品)
  • ConcreteProduct(具体产品)
  • Factory(抽象工厂)
  • ConcreteFactory(具体工厂)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 抽象产品
interface Logger{
public void writeLog();
}

// 具体产品
class DatabaseLogger implements Logger{
public void writeLog(){
System.out.println("数据库日志记录");
}
}

class FileLogger implements Logger {
public void writeLog(){
System.out.println("文件日志记录");
}
}

// 抽象工厂
interface LoggerFactory{
public Logger createLogger();
}

// 具体工厂
class DatabaseLoggerFactory implements LoggerFactory{
public Logger createLogger(){
Logger logger = new DatabaseLogger();
return logger;
}
}

// 具体工厂
class FileLoggerFactory implements LoggerFactory{
public Logger createLogger(){
Logger logger = new FileLogger();
return logger;
}
}

优点:

  1. 用户只需关心所需产品对应的工厂,无需关心创建细节甚至是具体的产品类名。
  2. 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。
  3. 当添加新产品时,只需添加新的产品具体类和具体工厂即可,完全符合开闭原则。

缺点:

  1. 当添加新产品的时候需要编写新的具体产品类和具体工厂,一定程度上增加了系统的复杂度
  2. 抽象层增加了系统的抽象性和理解难度(?我怎么感觉抽象更好)

适用场景:

  1. 客户端不需要知道所需要的对象的类
  2. 抽象工厂类通过其子类来指定创建哪个对象(面向对象多态和里氏代换原则)

抽象工厂模式

抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为 Kit 模式,它是一种对象创建型模式。

  • AbstractFactory(抽象工厂)
  • ConcreteFactory(具体工厂)
  • AbstractProduct(抽象产品)
  • ConcreteProduct(具体产品)

抽象工厂声明一组创建一族产品的方法

具体工厂实现创建一族产品的具体方法

添加产品族只需要继承或实现抽象工厂即可,符合开闭原则

添加产品等级结构需要改抽象工厂,一改动全身,不符合开闭原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 抽象工厂
abstract class AbstractFactory {
public abstract AbstractProductA createProductA();
public abstract AbstractProductB createProductB();
}

// 具体工厂
class ConcreteFactory1 extends AbstractFactory {
public AbstractProductA createProductA(){
return new ConcreteProductA1();
}

public AbstractProductB createProductB(){
return new ConcreteProductB1();
}
}

优点:

  1. 抽象工厂模式隔离了具体类的生成,改变具体工厂的实例就可以改变系统行为
  2. 当一个产品族被设计一起工作时,它能保证客户端始终使用用一个产品族的对象
  3. 增加新的产品族很方便,无需修改已有系统

缺点:

  1. 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,违背开闭原则

适用场景:

  1. 系统不依赖于产品类实例的创建的细节
  2. 系统中有多于一个的产品族,且每次只使用一个产品族
  3. 属于一个产品族的产品一起使用,这一约束必须在系统设计中体现出来
  4. 产品等级结构稳定,设计完成之后,不会再向系统中增加或删除等级结构

原型模式

原型对象(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过克隆这些原型创建新的对象。

  • Prototype(抽象原型类)
  • ConcretePrototype(具体原型类)

优点:

  1. 当创建一个新的对象实例较为复杂时,是原型模式通过复制一个现有的实例可以提高新实例的创建效率
  2. 扩展性好,面向抽象原型类进行编程,在增加或较少具体原型类对系统没有任何影响
  3. 原型模式提供简单的创建结构
  4. 可以使用深克隆的方式保存对象的状态

缺点:

  1. 每一类需要配备一个克隆方法,克隆方法位于类的内部,改造时需要修改,违背开闭原则
  2. 实现深克隆需要层层遍历,实现可能比较麻烦

适用场景:

  1. 创建新对象的成本比较大
  2. 系统需要保存对象的状态,而对象的状态很小,可以使用原型模式加备忘录模式
  3. 需要避免使用分层次的工厂类创建分层次的对象

建造者模式

建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

  • Builder(抽象建造者):它为创建一个产品的 Product 对象的各个部件指定抽象方法
  • ConcreteBuilder(具体建造者)
  • Product(产品角色)
  • Director(指挥者),指挥如何创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 产品
class Product{
private String partA;
private String partB;
private String partC;

//getter/setter
}

// 抽象建造类
abstract class Builder{
protected Product product = new Product();

public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();

public Product getResult(){
return product;
}
}

// 指挥类
class Director{
private Builder builder;

public Director(Builder builder){
this.builder = builder;
}

public void setBuilder(Builder builder){
this.builder = builder;
}

public Product construct()[
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
]
}

优点:

  1. 使用相同的创建过程,不同的具体建造类可以创建不同的产品对象
  2. 建造者之间相对独立,系统扩展方便,符合开闭原则
  3. 可以精细控制产品的创建过程

缺点:

  1. 如果产品的组成部分差异性大,不适合使用建造者模式
  2. 如果产品内部结构复杂且多变,使用建造者模式会使系统变得很庞大

适用场景:

  1. 需要生成的产品对象有复杂的内部结构
  2. 需要生成的产品对象属性相互依赖,需要指定其生成顺序
  3. 建造者模式通过引入指挥者类,将创建过程与建造者类和客户端分离开来
  4. 使用相同的步骤创建不同的产品

结构性模式

适配器模式

适配器模式(Adapter Pattern):将一个接口转换成客户端希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装类(Wrapper)。

  • Target(目标抽象类)
  • Adapter(适配器类)
  • Adaptee(适配者类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 对象适配器模式,通过与待适配对象实现关联关系
class Adapter extends Target {
private Adaptee adaptee;

public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
public void request(){
adaptee.specificRequest();
}
}

// 类适配器模式
class Adapter extends Adaptee implements Target {
public void request(){
specificRequest();
}
}

// 双向适配器模式
class Adapter implements Target,Adaptee {
private Target target;
private Adaptee adaptee;

public Adapter(Target target){
this.target = target;
}

public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}

public void request(){
adaptee.specificRequest();
}

public void specificRequest(){
target.request();
}
}

缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象实现该接口,并为接口中的每个方法提供一个默认实现,那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法得到情况。

  • ServiceInterface(适配器接口)
  • AbstractServiceClass(缺省适配器类)
  • ConcreteServiceClass(具体业务类)

优点:

  1. 引用适配器类重用现有的适配类,无需修改原有结构
  2. 将具体的业务封装在适配者类中,提高适配者类的复用性
  3. 对于对象适配器模式可以将多个不同的适配者适配到同一个目标

缺点:

  1. 不支持多继承的语言,一个类最多只能适配一个适配者类
  2. 适配者不能是最终类
  3. 类适配器模式的目标抽象类只能是接口
  4. 对象适配模式在适配一个适配者类时可以先创建一个适配者类的子类进行方法的覆盖,再对适配者子类进行适配,实现过程复杂

适用场景:

  1. 系统需要使用一些现有的类,而这些类的接口不符合系统的需要
  2. 创建一个重复使用的类,用于一些彼此没有太大关联的类

桥接模式

桥接模式(Bridge Pattern):将抽象部分与其实现部分分离,使它们都可以独立地变化。它是一个对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

  • Abstraction(抽象类)
  • RefinedAbstraction(扩充抽象类)
  • Implementor(实现类接口)
  • ConcrateImplementor(具体实现类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 实现类接口
interface Implementor{
public void operationImpl();
}

// 抽象类
abstract class Abstraction {
protected Implementor impl;

public void setImpl(Implementor impl){
this.impl = impl;
}

public abstract void operation();
}


// 扩充抽象类
class RefinedAbstraction extends Abstraction {
public void operation(){
impl.operationImpl();
}
}

优点:

  1. 分离抽象接口及其实现部分。
  2. 桥接模式可以取代多层继承方案
  3. 桥接模式提高了系统的可扩展性,在两个维度中任意扩展一个维度,都不要修改原有的系统。

缺点:

  1. 桥接模式增加了系统的理解和设计难度,需要开发者对抽象层进行设计和编程(感觉这是好处,觉得它难就是它的缺点我有点不赞同,要迎难而上)
  2. 需要正确识别系统中两个独立变化的维度

适用场景:

  1. 如果系统需要抽象类和具体类中增加更多的灵活性,避免在层次间继承关系,可以使用桥接模式在抽象层建立关联关系
  2. 抽象部分和实现部分可以以继承的方式单独扩展而互不影响
  3. 一个类存在两个或多个独立变化的维度
  4. 对于不希望使用继承或多层继承导致系统类急剧增加的系统,桥接模式尤为适用

组合模式

组合模式(Composite Pattern):组合多个对象形成树形结构以表示具有整体一部分关系的层次结构。组合模式对单个对象和组合对象的使用具有一致性,组合模式又称为整体-部分(Part-Whole)模式。

  • Component(抽象构件)
  • Leaf(叶子构件)
  • Composite(容器构件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 抽象构件,客户端对抽象构件进行编程
abstract class Component{
public abstract void add(Component c);
public abstract void remove(Component c);
public abstract Component getChild(int i);
public abstract void operation();
}

// 叶子构件
class Leaf extends Component {
public void add(Component c){

}
public void remove(Component c){

}
public Component getChild(int i){
return null;
}
public void operation(){

}
}

// 容器构件
class Composite extends Component {
private ArrayList<Component> list = new ArrayList<Compontnt>();

public void add(Component c){
list.add(c);
}
public void remove(Component c){
list.remove(c);
}
public Component getChild(int i){
return (Component)list.get(i);
}
public void operation(){
for(Object obj: list){
(Component)obj.operation();
}
}
}

优点:

  1. 组合模式可以清楚定义分层次的复杂对象
  2. 客户端一致地使用组合结构或单个对象
  3. 扩展增加新的容器构件和叶子构件方便
  4. 通过叶子对象和容器对象的递归组合,可形成复杂的树形结构

缺点:

  1. 增加新构件时难以对容器中的构建类进行限制

适用场景:

  1. 具体整体和部分层次结构中
  2. 使用面向对象语言系统处理树形结构
  3. 分离叶子对象和容器对象

装饰模式

装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象的功能来说,装饰模式比子类实现更为灵活。

  • Component(抽象构件)
  • Decorator(抽象装饰类)
  • ConcreteDecorator(具体装饰类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 抽象装饰类
class Decorator implements Component{
private Component component;

public Decorator(Component component){
this.component = component;
}

public void opeartion(){
component.operation();
}
}

// 具体抽象类
class ConcreteDecorator extends Decorator{
public ConcreteDecorator(Component component){
super(component);
}

public void operation(){
super.operation();
addedBehavior();
}

public void addedBehavior(){

}
}

注意事项:

  1. 尽可能保持装饰类的接口和被装饰类的接口相同
  2. 尽量保持具体构件类是一个轻类
  3. 如果只有一个具体构件类,那么可以抽象装饰类直接作为该具体构件类的子类

优点:

  1. 扩展一个对象的功能相比较继承更灵活
  2. 可以通过动态的方法进行对象功能的扩展
  3. 可以对一个对象进行多次装饰
  4. 具体构件类和具体装饰类可以独立变化

缺点:

  1. 装饰模式会设置非常多小对象,不利于管理
  2. 装饰模式比继承更容易出错,排错更困难

适用场景:

  1. 在不像其他对象的情况下,动态、透明地给单个对象添加职责
  2. 不能采用继承的方式对系统进行扩展或采用继承对系统扩展和维护不利时

外观模式

外观模式(Facade Pattern):外部与一个子系统的通信通过一个统一的外观角色进行,为子系统中的一组接口提供一个一致的入口,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。(子系统是一个广义的概念,它可以是一个类、一个功能模块、系统的一个组成部分或者一个完整的系统)。

  • Facade(外观角色)
  • SubSystem(子系统角色)
1
2
3
4
5
6
7
8
9
10
11
class Facade{
private SubSystemA obj1 = new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
private SubSystemC obj3 = new SubSystemC();

public void method(){
obj1.method();
obj2.method();
obj3.method();
}
}

优点:

  1. 客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目
  2. 实现了子系统与客户端之间的松耦合关系,子系统变化不会影响到客户端
  3. 一个子系统的修改不会影响到其他子系统
  4. 只是提供了一个子系统的统一访问入口,不影响客户端直接使用子系统

缺点:

  1. 不能很好地限制客户端直接使用子系统
  2. 设计不当,可能导致增加新的子系统时可能需要修改外观类的源代码

适用场景:

  1. 当要为访问一系列复杂的子系统提供一个简单入口时
  2. 客户端程序与多个子系统存在很大的依赖性,利用外观模式解耦
  3. 在层次化结构中,使用外观模式定义系统中每一层的入口

享元模式

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只是用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。

  • Flayweight(抽象享元类)
  • ConcreteFlyweight(具体享元类)
  • UnsharedConcreteFlyweight(非共享具体享元类)
  • FlyweightFactory(享元工厂类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 享元工厂类
class FlyweightFactory{
private HashMap flyweight = new HashMap();

public Flyweigth getFlyweight(String key){
if(flyweight.containsKey(key)){
return (Flayweight)flyweight.get(key);
}else {
Flyweight fw = new ConcreteFlyweight();
flyweight.put(key, fw);
return fw;
}

}
}

// 抽象享元类
class Flyweight {
private String intrinsicState;

public Flyweight(String intrinsicState){
this.intrinscState = intrinsicState;
}

public void operation(String extrinsicState){

}
}

优点:

  1. 可以极大减少内存中对象的数量
  2. 享元模式的外部状态相对独立,不会影响其内部状态,从而使享元对象可以在不同环境被共享

缺点:

  1. 分离出内部状态和外部状态,使得程序逻辑变得复杂
  2. 为了对象共享,需要将享元对象的部分状态外部化

适用场景:

  1. 以恶系统中有大量相同或者相似的对象,造成内存的大量耗费
  2. 对象的大部分状态可以外部化,可以将这些外部状态传入到对象中
  3. 维护一个存储享元对象的享元池,需要耗费一定的系统资源,在需要多次重复使用享元对象才值得使用

代理模式

代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制原对象的引用。

  • Subject(抽象主题角色)
  • Proxy(代理主题角色)
  • RealSubject(真实主题角色)
1
2
3
4
5
6
7
8
9
10
11
12
13
class Proxy implements Subject {
private RealSubject realSubject = new RealSubject();

public void preRequst(){}

public void request(){
preRequest();
realSubject.request();
postRequest();
}

public void postRequest(){}
}

常用的代理模式:

  1. 远程代理(Remote Proxy):为一个不同的地址空间的对象提供一个本地的代理对象。远程代理又称为大使(Ambassador)。
  2. 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗较小的对象来表示,真实的对象只在需要时才会被真正创建。
  3. 保护代理(Protect Prxoy):控制一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  4. 缓冲代理(Cache Proxy):为一个目标操作的结构提供临时的存储空间,以便多个客户端可以共享这些结构。
  5. 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外操作。

优点:

  1. 代理模式协调调用者和被调用者,在一定程度降低系统的耦合度
  2. 客户端根据抽象主题角色编程,增加更换代理类无须修改源代码
  3. 远程代理位于两个不同地址空间对象的访问提供一个实现机制
  4. 虚拟代理通过一个消耗资源较少的对象来代表资源消耗较多的对象,节省系统开销
  5. 保护代理可以控制对一个对象的访问权限

缺点:

  1. 客户端和真实主题之间增加了代理对象,请求速度变慢。
  2. 实现代理模式需要额外的工作

适用场景:

  1. 当客户端需要访问远程主机对象时,使用远程代理
  2. 当需要一个消耗资源较少的对象来代表消耗资源较多的对象时,使用虚拟代理
  3. 当控制一个对象的访问,使用保护代理
  4. 当需要某一个频繁访问的操作结果提供临时存储空间使用缓冲代理
  5. 当需要为一个对象的访问提供一些额外操作时,使用智能引用代理

行为型模式

职责链模式

职责链模式(Chain of Responsibility Pattern):避免将请求发送者与接收者耦合在一起,让多个对象都有机会接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

  • Handler(抽象处理者)
  • ConcreteHandler(具体处理者)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 抽象处理类
abstract class Handler {
protected Handler successor;

public void setSuccessor(Handler successor){
this.successor = successor;
}
public abstract void handleRequest(String request);
}

// 具体处理类
class ConcreteHandler extends Handler {
public void handleRequest(String reqeust){
if(请求满足条件){
// 处理请求
}else {
this.successor.handleRequest(request); // 转发请求
}
}
}

优点:

  1. 一个对象无需知道哪一个对象会处理此请求,客户端负责链的创建,降低了系统的耦合度
  2. 请求处理对象仅需维持一个其后继者的引用,而不需要维持所有的,简化对象连接
  3. 在系统中增加新的具体处理者无需修改原有系统的代码,只需客户端重新建链即可

缺点:

  1. 请求可能一直到末端也没有处理
  2. 对于长的职责链,代码调试不太方便
  3. 建链不当可能造成循环调用,导致系统陷入死循环

适合场景:

  1. 有多个对象可以处理同一个请求,具体哪个对象处理运行时确定,客户端无需知道是哪个具体对象处理
  2. 在不明确指定接收者的情况下,向多个对象中的提交一个请求
  3. 可动态指定一组对象处理请求,客户端可以改变链中处理者之间的先后次序

命令模式

命令模式(Command Pattern):将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。别名为动作(Action)模式或事务(Transaction)模式。

  • Command(抽象命令类)
  • ConcreteCommand(具体命令类)
  • Invoker(调用者)
  • Receiver(接收者)

命令模式的本质就是对请求进行封装,一个请求对应于一个命令,将发出的命令的职责和执行命令的责任分隔开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 抽象命令类
abstract class Command {
public abstract void execute();
}

// 调用者
class Invoker {
private Command command;

public Invoker(Command command){
this.command = command;
}

public void setCommand(Command command){
this.command = command;
}

public void call(){
command.execute();
}
}

// 具体命令类
class ConcreteCommand extends Command {
private Receiver receiver;

public void execute(){
receiver.action();
}
}

// 接收者
class Reciver {
public void action(){
// 具体操作
}
}

优点:

  1. 降低系统耦合度,请求者和接收者之间不存在直接引用
  2. 新的命令可以很容易加到系统中
  3. 比较容易设计一个命令队列或宏命令(组合模式)
  4. 为请求的撤销和恢复操作设计一种设计和实现方案

缺点:

  1. 可能会导致系统有过多的具体命令类

适合场景:

  1. 系统需要调用者和接收者解耦
  2. 系统需要在不同的时间指定请求,将请求排队和执行请求。
  3. 系统需要支持命令的撤销和恢复操作
  4. 系统需要一组操作组合形成宏命令

解释器模式

解释器模式(Interpreter Pattern):定义一个语言的文法,并且建立一个解释器来接收该语言中的句子,这里的语言是指使用规定格式和语法的代码。

  • AbstractExpression(抽象表达式)
  • TerminalExpression(终结符表达式)
  • NonterminalExpression(非终结符表达式)
  • Context(环境类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 抽象表达式
abstract class AbstractExpression {
public abstract void interpret(Context ctx);
}

// 终结符表达式
class TerminalExpression extends AbstractExpression {
public void interpret(Context ctx){
// 终结符表达式的解释操作
}
}

// 非终结符表达式
class NonterminalExpression extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;

public NonterminalExpression(AbstractExpression left, AbstractExpression right){
this.left = left;
this.right = right;
}

public void interpret(Context ctx){
// 递归调用每一个组成部分的 interpret 方法
// 在递归调用时指定组成部分的连接方式,即非终结符的功能
}
}

// 环境类
class Context {
private HashMap map = new HashMap();

public void assign(String key, String value){
// 往环境类中设值
}

public String lookup(String key){
// 获取存储在环境类中的值
}
}

优点:

  1. 易于改变和扩展文法
  2. 每一条文法规则都可以表示一个类
  3. 实现文法较为容易
  4. 增加新的解释表达式较为方便,只需增加终结符表达式或非终结符表达式

缺点:

  1. 复杂文法难以维护,每一个规则都至少需要定义一个类
  2. 执行效率低,大量递归和循环

适用场景:

  1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
  2. 一些重复出现的问题可以用一种简单的语言来表达
  3. 一个语言的文法较为简单
  4. 执行效率不是关键问题

迭代器模式

迭代器模式(Interator Pattern):提供一个方法来访问聚合对象,而不是暴露这个对象的内部表示,其别名为游标。

  • Iterator(抽象迭代器)
  • ConcreteIterator(具体迭代器)
  • Aggregate(抽象聚合类)
  • ConcreteAggregate(具体聚合类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 抽象迭代器
interface Iterator {
public void first();
public void next();
public boolean hasNext();
public Object currentItem();
}

// 具体迭代器类
class ConcreteIterator implements Iterator {
private ConcreteAggregate objects;
private int cursor;
public ConcreteIterator(ConcreteAggregate objects){
this.objects = objects;
}

public void first() {};
public void next() {};
public boolean hasNext() {};
public Object currentItem() {};
}

// 抽象聚合类
interface Aggregate {
Iterator createIterator();
}

// 具体聚合类
class ConcreteAggregate implements Aggregate {
public Iterator createIterator(){
return new ConcreteIterator(this);
}
}

优点:

  1. 支持不同的方式遍历一个聚合对象。
  2. 迭代器简化了聚合类,通过迭代器来实现聚合类的遍历等方法
  3. 迭代器引入抽象层,在增加新的聚合类和迭代器类都很方便

缺点:

  1. 迭代器模式将存储数据和遍历数据的职责分开,因此增加聚合类时相应要增加迭代器类,增加了系统的复杂性
  2. 抽象迭代器的设计难度较大,应充分考虑到系统未来的扩展

适用场景:

  1. 访问一个聚合对象的内容而无须暴露它的内部表示
  2. 需要为一个聚合对象提供多种遍历方式
  3. 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不用的聚合类提供不同的遍历方式

中介者模式

中介者模式(Mediator Pattern):用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互。

  • Mediator(抽象中介者)
  • ConcreteMediator(具体中介者)
  • Colleague(抽象同事类)
  • ConcreteColleague(具体同事类)

中介类的两个作用:

  1. 中转作用。通过中介者提供的中转作用,各个同事对象就不再显式引用其他同事,当需要其他同事通过中介者间接调用
  2. 协调作用。中介者可对同事的请求进一步处理,将同事成员之间的关系行为进行分离和封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 抽象中介者
abstract class Mediator {
protected ArrayList<Colleague> colleagues;

public void register(Colleagure colleague){
colleagues.add(colleague);
}

public abstract void operation();
}

// 具体中介者
class ConcreteMediator extends Mediator {
public void operation(){
((Colleague)(colleagues.get(0))).method1();
}
}

// 抽象同事类
abstract class Colleague {
protected Mediator mediator;

public Colleague(Mediator mediator){
this.meditor = mediator;
}

public abstract void method1();

public void method2(){
mediator.operation();
}

}

// 具体同事类
class ConcreteColleague extends Colleague {
public ConcreteColleague(Mediator mediator){
super(mediator);
}

public void method1(){

}
}

优点:

  1. 简化对象之间的交互
  2. 将各同事对象解耦
  3. 减少大量同事子类生成

缺点:

  1. 在具体中介类中包含了大量同事之间的交互细节,可能导致中介类过于复杂难以维护

适用场景:

  1. 系统中对象之间存在复杂的引用关系,系统结构混乱难以理解
  2. 一个对象由于引用了其他很多对象通信,导致难以复用该对象
  3. 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类

备忘录模式

备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后对象恢复到原先保存的状态。

  • Originator(原发器)
  • Memento(备忘录)
  • Caretaker(负责人)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 原发器
public class Originator {
private String state;
public Originator(){}

public Memento createMemento(){
return new Memento(this);
}

public void restoreMemento(Memento m){
state = m.state;
}

// getter/setter
}

// 备忘录
class Memento {
private String state;

public Memento(Originator o){
state = o.getState();
}

// getter/setter
}

// 负责人
public class Caretaker {
private Memento memento;

public Memento getMemento(){
return memento;
}

// getter/setter
}

优点:

  1. 提供一种状态恢复的实现机制
  2. 实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示

缺点:

  1. 资源消耗过大,每保存一次对象的状态都需要消耗一定的系统资源

适用场景:

  1. 保存一个对象在某一时刻的全部状态或部分状态
  2. 防止外界对象破坏一个对象历史状态的封装性,避免对象历史状态实现细节暴露给外界对象

观察者模式

观察者模式(Observer Pattern):定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。也可称为发布-订阅(Publish/Subscribe)模式、模型-视图(Modal/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

  • Subject(目标)
  • ConcreteSubject(具体目标)
  • Observer(观察者)
  • ConcreteObserver(具体观察者)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 目标
abstract class Subject {
protected ArrayList<Observer> observers = new ArrayList<>();

public void attach(Observer observer){
observers.add(observer);
}

public void detach(Observer observer){
observers.remove(oberver);
}

public abstract void notify();
}

// 具体目标类
class ConcreteSubject extends Subject {
public void notify(){
for(Observer ob: observers){
ob.update();
}
}
}

// 抽象观察类
interface Observer{
void update();
}

// 具体观察者
class ConcreteObserver implements Observer {
public void update(){
// 具体响应代码
}
}

优点:

  1. 实现了表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制
  2. 在观察目标和观察者之间建立一个抽象的耦合
  3. 支持广播通信,简化一对多系统的设计
  4. 增加新的具体观察者无需修改原有系统代码,具体观察者和观察目标不存在关联关系时增加新的观察目标也很方便

缺点:

  1. 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者通知很花时间
  2. 观察者和观察者目标之间存在循环依赖,观察目标触发循环调用可能导致系统崩溃
  3. 没有相应的机制让观察者知道所有观察的目标如何变化而仅仅知道观察目标发生了变化

适用场景:

  1. 一个抽象模型有两个方面,其中一个方面依赖于另一个方面
  2. 一个对象的改变导致一个或多个其他对象也发生改变
  3. 需要在系统中创建一个触发链

状态模式

状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

  • Context(环境类)
  • State(抽象状态类)
  • ConcreteState(具体状态类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 抽象状态类
abstract class State {
public abstract void handle();
}

// 具体状态类
class ConcreteState extends State {
public void handle(){
// 方法具体实现代码
}
}

// 环境类
class Context {
private State state;
private int value;

public void setState(State state){
this.state = state;
}

public void request(){
state.handle();
}

}

状态改变的两种方式:

  1. 统一由环境类来负责状态之间的转换
  2. 由具体状态类负责状态之间的转换

优点:

  1. 封装状态的转换规则,对状态转换代码进行统一管理
  2. 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象
  3. 允许状态转换逻辑与状态对象合成一体,而不是一个提供巨大的条件语句块
  4. 可以让多个环境对象共享一个状态对象

缺点:

  1. 状态模式的使用增加系统中类和对象的个数
  2. 程序结构与实现较为复杂
  3. 开闭原则支持不好

适合场景:

  1. 对象的行为依赖它的状态,状态的改变将导致行为的变化
  2. 在代码中包含大量与对象状态有关的条件语句

策略模式

策略模式(Strategy Pattern):定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也成为政策模式(Policy)。

  • Context(环境类)
  • Strategy(抽象策略类)
  • ConcreteStrategy(具体策略类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 抽象策略类
abstract class AbstractStrategy{
public abstract void algorithm();
}

// 具体策略类
class ConcreteStrategy extends AbstractStrategy {
public void algorithm(){
// 算法A
}
}

// 环境类
class Context {
private AbstractStrategy strategy;

public void setStrategy(AbstractStrategy strategy){
this.strategy = strategy;
}

public void algorithm(){
strategy.algorithm();
}
}

优点:

  1. 完美支持开闭原则,用户可以在不修改原有系统代码的基础上增加新的算法或行为
  2. 提供类管理相关算法族的办法,恰当使用继承可以将公共的代码抽取到抽象策略类中
  3. 提供了替换继承关系的办法。
  4. 避免了多重条件选择语句
  5. 提供了算法复用机制,可以在不同环境类中使用这些策略

缺点:

  1. 客户端必须知道所有的策略类,然后决定使用哪一个
  2. 策略模式导致系统出现很多具体策略类
  3. 无法同时在客户端使用多个策略类

适用场景:

  1. 一个系统需要动态地在几种算法中选择一种
  2. 一个对象有很多的行为,这些行为转移到相应的具体策略类中可以避免使用难以维护的多重条件选择语句
  3. 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,提高算法的保密性与安全性

模板方法模式

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

  • AbstractClass(抽象类)
  • ConcreteClass(具体类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 抽象类
abstract class AbstractClass {

// 模板方法
public void templateMethod(){
primitiveOperation1();
primitiveOperation2();
primitiveOperation3();
}

// 具体方法
public void primitiveOperation(){
// 实现代码
}

// 抽象方法
public abstract void primitiveOperation2();

// 钩子方法
public void primitiveOperation3(){}
}

// 具体类
class ConcreteClass extends AbstractClass {
public void primitiveOperation2(){
// 实现代码
}
public void primitiveOperation3(){
// 实现代码
}
}

优点:

  1. 在子类实现详细的处理算法时并不会改变算法中的步骤的执行次序
  2. 将公共方法放入父类中,通过子类实现不同的行为,它鼓励使用继承来实现代码复用
  3. 实现反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否执行
  4. 不同子类提供的方法的不同实现,更换和增加子类很方便,符合单一职责原则和开闭原则

缺点:

  1. 需要为每一个基本方法的不同实现提供一个子类,如果父类可变方法太多,将导致类的个数增加,可结合桥接模式来设计

适用场景:

  1. 对一个复杂算法进行分割
  2. 各自类公共的行为应该提取并集中到一个公共父类中
  3. 通过子类来决定父类算法中某一个步骤需要执行,实现子类对父类的反向控制

访问者模式

访问者模式(Visitor Pattern):提供一个作用于某对象结构中的各元素的操作表示,它使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

  • Visitor(抽象访问者)
  • ConcreteVisitor(具体访问者)
  • Element(抽象元素)
  • ConcreteElement(具体元素)
  • ObjectStructure(对象结构)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 抽象访问者
abstract class Visitor {
public abstract void visit(ConcreteElementA elementA);
public abstract void visit(ConcreteElementB elementB);

public void visit(ConcreteElementC elemtentC){
// 元素 ConcreteELementC 操作代码
}
}

// 具体访问者
class ConcreteVisitor extends Visitor {
public void visit(ConcreteELementA elementA){
// 元素 ConcreteElementA 操作代码
}

public void visit(ConcreteElementB elementB){
// 元素 ConcreteElementB 操作代码
}
}

// 抽象元素类
interface Element {
public void accept(Visitor visitor);
}

// 具体元素类
class ConcreteElement implements Element {
public void accept(Visitor visitor){
visitor.visit(this);
}

public void operationA(){
// 业务方法
}
}

// 对象结构
class ObjectStructure {
private ArrayList<Element> list = new ArrayList<>();

public void accept(Visitor visitor){
for(Element e: list){
e.accept(visitor);
}
}

public void addElement(Element element){
list.add(element);
}
}

优点:

  1. 增加新的访问操作很方便
  2. 将有关元素对象的访问集合到一个访问者对象中
  3. 让用户能够在不修改原有元素层次结构的情况下,定义作用于该层次结构的操作

缺点:

  1. 增加新的元素类很困难
  2. 破坏封装,访问者模式要求访问者对象访问并调用每一个元素对象的操作

适用场景:

  1. 一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖具体类型的操作
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作
  3. 对象结构中对象对应的类很少变化,但经常需要在此对象结构上定义新的操作

设计模式的艺术一书的阅读笔记
https://reajason.vercel.app/2022/03/17/DesignPattern/
作者
ReaJason
发布于
2022年3月17日
许可协议