01-设计模式

Charlie

现有设计经验的总结

模式:问题和约束可以反复使用

创建型模式
结构型模式
行为型模式

1. 策略模式

  • Strategy/Policy

  • 使用哪些原则

    1. 单一职责
      • 封装变化
      • Seperation
    2. 依赖倒转
    3. 合成复用

delegate: 委托另外一个模块,通过引用调用
OOA到OOD:添加辅助类,设计初期会增加复杂性

image.png

1.1. 定义

完成的任务+实现的方式

  • 让算法的变化独立于使用它的客户端
  • 定义一个可变的算法族,并将它们封装起来

1.2. 适用性

  1. 相关的类只在某些行为上不同,每个类在这些行为上都有合适的策略(多个类)
  2. 同一个算法需要不同的实现
  3. 算法封装不同数据结构
  4. 一个类定义了有很多行为,使用条件判断觉得使用哪个行为(一个类里有多种行为)

1.3. 影响

原来不需要知道类的细节,但使用策略模式之后需要暴露细节,客户必须理解每一种策略的差异和哪些任务适合哪些细节

Design Pattern比Framework更高层

2. 简单工厂模式

2.1. 👍定义

  • Simple Factory Pattern/Static Factory Pattern

    • 往往是静态方法
    • 根据参数不同,返回不同类的实例
      • 复用创建方法
    • 属于类创建型模式
  • 原则

    • 最小知识原则,构造方法无法抽象但是可以封装

2.2. 结构

  • Factory: 工厂角色
  • Product: 抽象产品角色
  • ConcreteProduct: 具体产品角色

返回的是抽象的产品类,符合依赖倒置原则

image.png

2.3. 分析

  1. 将对象的创建和具体业务处理分离,降低系统耦合(把具体耦合替换成抽象耦合),使得两者修改起来更容易。
  2. 工厂方法是静态方法,而且只需要传入简单参数,因此可以把调用时需要传入的参数写在配置文件中,无需修改Java源代码。
  3. 工厂类的职责过重,增加新的产品需要修改工厂类判断逻辑,违反开闭原则
  4. 当你需要什么,只需要传入一个正确的参数,就可以获取对象,无需知道其创建的细节

2.4. 扩展

有些情况下可以把静态工厂方法放到抽象产品类中

image.png

3. 工厂方法模式

3.1. 动机

简单工厂违反了开闭原则

image.png

使用抽象工厂,抽象的工厂方法只负责生产抽象的产品类,把具体的按钮了创建过程嫁给专门的工厂子类来完成。
image.png

3.2. 👍定义

  • Factory Method Pattern/ Virtual Constructor/ Polymorphic Factory
    • 工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象
    • 目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类
    • 类创建模式
  • 可以时间不修改原有代码的情况下,增加新的产品

3.3. 结构

image.png

用户代码只使用抽象层,与具体实现解耦

抽象工厂类:

1
2
3
public abstract class PayMethodFactory{
public abstract AbstractPay getPayMethod();
}

具体工厂类:

1
2
3
4
5
public class CashPayFactory extends PayMethodFactory{
public AbstractPay getPayMethod(){
return new CashPay();
}
}

客户代码:

1
2
3
4
5
PayMethodFactory factory;
AbstractPay payMethod;
factory=new CashPayFactory();
payMethod =factory.getPayMethod();
payMethod.pay();

使用XML指定抽象工厂具体类型的客户代码:

1
2
3
4
5
PayMethodFactory factory;
AbstractPay payMethod;
factory=(PayMethodFactory)XMLUtil.getBean(); //getBean()的返回类型为Object,此处需要进行强制类型转换
payMethod =factory.getPayMethod();
payMethod.pay();

3.4. 适用性

  1. 一个类不需要知道它所需要的对象的类
  2. 一个类通过其子类来指定创建哪个对象
  3. 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关系是哪一个工厂类创建产品子类,需要时再动态指定

只能扩展产品类型
支持开闭原则

4. 抽象工厂模式

产品等级结构:即产品的继承结构 ,如抽象类是电视机,子类是不同具体品牌
产品族:由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产电视机、冰箱,海尔电视机属于电视机等级结构,冰箱属于冰箱等级结构

4.1. 👍定义

  • 区别

    1. 工厂模式:针对一个产品等级结构
    2. 抽象工厂:需要面对多个产品等级结构
  • Abstract Factory Pattern/Kit Pattern

    • 提供创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
    • 对象创建模式:扩展能力由对象耦合产生,所以不是类创建模式

image.png

1
2
3
4
public abstract class AbstractFactory{
public abstract AbstractProductA createProductA();
public abstract AbstractProductB createProductB();
}
1
2
3
4
5
6
7
8
public class ConcreteFactory1 extends AbstractFactory{
public AbstractProductA createProductA(){
return new ConcreteProductA1();
}
public AbstractProductB createProductB(){
return new ConcreteProductB1();
}
}

4.2. 分析

开闭原则的倾斜性

  • 优点:增加新的具体工厂和产品族很方便,符合开闭原则
  • 缺点:不能完全支持开闭原则,需要增加新的产品等级结构时需要修改抽象类
    • 不支持增加新的产品等级结构

5. 建造者模式

一个复杂对象有许多成员对象

5.1. 定义

  • **建造者模式(Builder Pattern)**:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
    • 表示:对象的静态内容
    • 构建过程可以复用,组件的表示可以分离
    • 属于对象创建型模式

封装变化,复用不变

5.2. 结构

  1. Product
  2. Builder:抽象建造者
  3. ConcreteBuilder:负责A, B, C等对象的创建
  4. Director:创建A,B,C的构建顺序
  • 为什么是对象创建模式?
    • 最重要的是通过Director和Builder的合成关系,而不是继承

image.png

  • 例子:
    • KFC的指挥者是服务员,而不是顾客

image.png

5.3. 分析

  • Director

    1. 隔离客户与生产过程
    2. 负责控制产品的生产过程
  • 为什么不对Director抽象提供各种策略?

    • 初衷就是为了隔绝用户和复杂的构造过程,如果再对Director做抽象,用户就需要知道各种复杂的构造过程

5.4. 适用性

  1. 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品
  2. 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
  3. 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  4. 对象的创建过程独立于创建该对象的类

2,3,4是为了分解复杂性;
1是为了封装变化

5.5. 化简

  1. 省略抽象建造者:如果系统中只需要一个具体建造者的话,可以省略掉抽象建造者。
  2. 省略指挥者:在具体建造者只有一个的情况下,如果抽象建造者角色已经被省略掉,那么还可以省略指挥者角色,让Builder角色扮演指挥者与建造者双重角色。

5.6. 建造者模式与抽象工厂模式的比较

• 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工
厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成
了一个产品族。
• 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产
品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,
而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,
它侧重于一步步构造一个复杂对象,返回一个完整的对象。
• 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那
么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完
整的汽车。

6. 原型模式

创建型模式
在面向对象系统中,使用原型模式来复制一个对象自身, 从而克隆出多个与原型对象一模一样的对象。

6.1. 定义

  • 原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。允许一个对象再创建一个可定制的对象,无需知道任何创建的细节。
    • 对象创建型模式

6.2. 结构

  1. Prototype:抽象原型类
    • 原型类有一个clone接口
  2. ConcretePrototype:具体原型类
  3. Client:客户类

image.png

6.3. 分析

抽象类:IS A关系,主职责
接口:非功能主职责的职责

copy需要根据具体类来执行,而且不是IS A关系

  • 在原型模式结构中定义了一个抽象原型类,所有的Java 类都继承自java.lang.Object,而Object类提供一个 clone()方法,可以将一个Java对象复制一份。因此在 Java中可以直接使用Object提供的clone()方法来实现对象的克隆,Java语言中的原型模式实现很简单。
  • 能够实现克隆的Java类必须实现一个标识接口Cloneable, 表示这个Java类支持复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个 CloneNotSupportedException异常
    • 把一些工作交给编译器

6.4. 适用性

  1. 创建新对象成本较大
  2. 系统要保存对象的状态
  3. 避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便

6.5. 扩展

  • 原型管理器:带管理器的原型模式,系统中有大量相似的对象

image.png

  • 相似对象的复制:通过原型模式获得相同对象后可以再对其属性进行修改,从而获取所需对象。

7. 状态模式

7.1. 动机

  • 状态:一个或多个变化的属性
  • 有状态的对象:有这样属性的对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。

UML中使用状态图来描述有状态对象的变化,一张图只能描述一个对象

如果写很多if来判断状态,不符合开闭模式

7.2. 结构

  1. Context: 环境类
  2. State: 抽象状态类
  3. ConcreteState: 具体状态类

image.png

7.3. 定义

  • 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类
    • 通过对象组合来减少类型定
  • 行为模式

7.4. 分析

  1. 引入一个抽象类,专门表示对象的状态变化,以及对象如何在每一种状态中表现出的行为
  2. Context应当尽量承担数据职责,State承担行为职责
    • 可能相互持有引用:State类持有Context类的引用是为了访问数据,Context类持有State类是为了执行行为
  3. 封装了状态转换规则
    • 自已完成行为切换,行为是具体的,会产生具体耦合。开闭原则支持不好,增加新的状态要修改负责状态转换的源代码。
    • 与策略模式区别:策略模式中策略的切换在Client中,由使用者切换。开闭原则支持好。
  4. 状态转换的触发者
    1. Context类
    2. Concrete State类

7.5. 适用性

  1. 对象的行为依赖于状态属性,并且随着属性改变
  2. 代码中由大量与状态有关的条件判断

7.6. 例子

image.png

image.png

  • 不使用状态模式:
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
if(state=="空闲")
{
if(预订房间)
{
预订操作;
state="已预订";
}
else if(住进房间)
{
入住操作;
state="已入住";
}
}
else if(state=="已预订")
{
if(住进房间)
{
入住操作;
state="已入住";
}
else if(取消预订)
{
取消操作;
state="空闲";
}
}
……
  • 使用状态模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
//重构之后的“空闲状态类”示例代码
……
if(预订房间)
{
预订操作;
context.setState(new 已预订状态类());
}
else if(住进房间)
{
入住操作;
context.setState(new 已入住状态类());
}
……

7.7. 扩展

  • 共享状态
    • 在有些情况下多个环境对象需要共享同一个状态,如果希望在系统中实现多个环境对象实例共享一个或多个状态对象,那么需要将这些状态对象定义为环境的静态成员对象。
  • 简单状态模式
    • 所有状态相互独立,无需转换。可以在客户端直接实例化状态类,然后设置到环境类中。
    • 遵循开闭原则,和策略模式只是含义不一样,设计完全相同。

8. 命令模式

8.1. 动机

  • 我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可。
  • 让发送者和接收者完全解耦,发送者完全不知道接收者是谁
    • 如果无法抽象,可以用wrapper

8.2. 定义

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

8.3. 结构

  1. Invoker
    • 被参数化
  2. Command
  3. Receiver

Invoker只知道Command类,不知道调用的是Receiver,也不知道命令具体怎么执行。
execute()是receiver.action()的wrapper方法。

image.png

线程池:同一个线程对象,传入不同Command,让它执行不同的命令

8.4. 分析

  1. 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递
    • 行为数据化,同一个行为可以在不同时间/空间执行
    • 参数化
    • 行为封装成对象,可以存储成队列
  2. 命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
  3. 本质:是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
  4. 每个命令都是一个操作:请求方发出请求,要求执行一个操作;接收方接收请求,执行操作
  5. 请求方和接收方解耦,使得Invoker和Command都可以被复用
  • 优点
    1. 降低耦合
    2. 容易加入新的命令
    3. 容易设计一个命令队列和宏命令(组合命令)
    4. 实现undo和redo
      • 在Command接口里除了execute()还可以有undo()

8.5. 例子

Invoker: Contoller
Receiver: Television

image.png

8.6. 适用场景

  1. 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
  2. 系统需要在不同的时间指定请求,将请求排队和执行请求
  3. 支持命令的Undo和Redo
  4. 需要将一组操作在一起,即支持宏命令

8.7. 扩展:宏命令

  1. 宏命令又称为组合命令,它是命令模式和组合模式联用的产物。
  2. 宏命令也是一个具体命令,不过它包含了对其他命令对象的引用,在调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法
  3. 宏命令的成员对象可以是简单命令,还可以继续是宏命令。执行 一个宏命令将执行多个具体命令,从而实现对命令的批处理
  4. 宏命令的undo:从ArrayList的尾到头做undo

image.png

9. 中介者模式

9.1. 动机

  • 多个用户之间的群聊,引入中间层,减少对象两两之间复杂的引用关系
    • 迪米特法则

9.2. 定义

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

9.3. 结构

image.png

  1. Mediator:抽象中介类
  2. ConcreteMediator: 具体中介类
  3. Colleague: 抽象同事类
  4. ConcreteColleague: 具体同事类

9.4. 分析

  1. 中介者模式使得对象之间的关系数量急剧减少image.png
  2. 为什么要多个中介者?
    • 修改对象交互关系时,不需要修改原来的中介关系,可以新增中介关系替换原有的关系。

9.5. 分析

  • 中介者承担两方面的职责:
    1. 中转作用(结构):通过中介的中转作用,各个同时不需要显示引用其他同时
    2. 协调作用(行为):中介者可以进一步对同事之间的关系进行封装,同事可以一致地与中介者进行交互,而不需要指明中方接着需要具体则么做,中介者根据内部的逻辑对同事的请求进行进一步处理,将同事成员的关系和行为进行分离和封装

9.6. 例子

image.png

9.7. 适用环境

  1. 系统中对象之间的引用关系很复杂,相互依赖关系混乱,难以复用
  2. 对象的交互行为很复杂。想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象交互的公共行为,如果需要改变行为则可以增加新的中介者类。

9.8. 扩展

  1. 中介者模式与迪米特法则

    • 在中介者模式中,通过创造出一个中介者对象,将系统中有关的对象所引用的其他对象数目减少到最少,使得一个对象与其同事之间的相互作用被这个对象与中介者对象之间的相互作用所取代。因此,中介者模式就是迪米特法则的一个典型应用。
  2. 图形界面

    1. 工厂,抽象工厂
    2. 命令模式
    3. 建造者模式
    4. 中介者模式
  • 标题: 01-设计模式
  • 作者: Charlie
  • 创建于 : 2024-03-05 15:03:00
  • 更新于 : 2024-04-01 10:15:42
  • 链接: https://chillcharlie357.github.io/posts/946442dc/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论