设计模式

引子

image.png

超类之间的某些行为对于子类来说,是不能实现的
橡皮鸭可以对fly方法进行override
但我们完全可以用接口的方法改变:

image.png

但是也有问题,如果需要更改很多只鸭子的飞行的行为,需要逐一改动

封装变化原则

区别应用中变化并且能起区分作用的部分,分开变化和不变的部分

  • 从而能够区分和封装变化的部分,可以在不影响其他部分的情况下做出改变
  • 把飞行,叫的行为封装成行为类,实现行为接口
  • 每个鸭子拥有一个抽象接口,从而可以赋予不同的飞的具体行为
  • 同理于叫
image.png image.png

面向接口编程而不面向实现

并非是鸭子的类来实现飞行的接口,是一些具体飞行行为实现飞行的接口

回想多态,被声明的类一般都是超类,即抽象类或者接口,所以负责声明的类并不需要知道具体的对象类型是什么,

组合优于继承

  • 组合带来了更多的灵活性
  • 组合封装了一系列的行为/算法,并且能够在运行的时候实时改变

策略模式

由多态形成的

http://c.biancheng.net/view/1378.html

image.png

定义

  • 定义并且包装一系列的策略/算法,并将其设计为可相互替换的(面向接口)
  • 通过策略让不同的使用者(Client)使用不同的算法

动机

  • 当我们需要用很多条件语句来定义行为的时候,消除条件语句
  • 这种hard-wiring的注入方法并不理想,替代继承
  • 不同的时候需要不同的算法,但是我们并不想支持我们并不实用的算法
  • 增加和改变新的算法的时候,将会非常困难

适用性

  • 许多行为中的一个行为用来配置类,许多相关的类的差别就在于行为的不同
  • 一个相同的算法/行为,可能会有不同的实现形式,基于不同tradeoff的考虑进行选择
  • 算法使用了客户不应该知道的数据,通过策略模式可以隐藏具体的算法实现,也就是数据结构的隐藏
  • 代替了条件语句的移入

缺点

  • 暴露了实现细节,客户必须了解不同的策略,如果要选择一个合适的策略/算法给Context使用,就必须知道这些策略到底有什么不同,可能就会暴露具体的实现问题
  • 通信开销,每个不同的算法,都共享了Strategy共享的接口,有些具体算法并不会用到通过接口传递给他们的信息。也就意味着使用具体算法的Context会创建一些永远用不到的参数
  • 增加了对象的数目

简单工厂模式

image.png

动机

  • 我们在使用一个工具的时候,他有许多相同层次的兄弟工具,修改了部分属性,并且他们都继承于一个相同的基类
  • 我们在使用这些工具的时候,不想知道这些工具到底叫做什么,我们只想知道表示不同工具的参数,并且有一个调用工具的方法,只要把参数传入给方法就可以获得对应的工具,例如给我一个圆的按钮,给我一个长的板凳

定义

  • 静态工厂方法
  • 可以根据参数的不同,返回不同类的实例
  • 定义了一个工厂类来负责创建其他类的实例
  • 被创建的实例通常都有共同的父类(可以是Abstract)

结构

  • Factory:工厂角色,一个
  • Product:抽象产品角色,一个
  • ConcreteProduct:具体产品角色,多个

分析

  • 需要什么就能得到什么,客户端只需要知道参数,不用知道创建细节
  • 对象的创建以及对象的业务处理分离,降低耦合性
  • 静态方法使用方便,参数甚至可以保存在配置文件中,不需要修改源代码
  • 工厂是知道具体的产品的,所以工厂需要依赖具体的产品而不是产品接口

优点

  • 责任的分割
  • 减少使用者的记忆量
  • 配置文件的引入避免了一些代码的修改

缺点

  • 工厂职责过重,增加新的产品需要修改工厂的判断逻辑,与开闭原则相互违背,
  • 工厂能否正常工作决定了系统能否正常工作
  • 系统拓展困难
  • 工厂角色无法形成基于继承的等级结构

适用性

  • 对象比较少
  • 客户不关心

实例

创建用户

image-20210624163312114

JDK

image.png

Java加密

image.png

拓展

抽象类可以没有抽象方法,抽象方法一定属于抽象类

  • 工厂类可以由抽象产品扮演,通过抽象产品来创建子类产品的实例
  • 使用静态工厂方法创建子类
image-20210624163753762

工厂方法模式

工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体 产品类

image.png

动机

  • 在简单工厂方法中,需要增加新的产品
  • 那么现在我们不设计一个按钮工厂类来统一负责所有产品的创建,而是每个具体产品对应着每个具体的工厂
  • 这样就可以在不修改具体工厂类的情况下引进新的产品,更加符合开闭原则

定义

  • 工厂模式,或者多态工厂模式,或者虚拟构造器模式
  • 工厂抽象类负责定义创建产品对象的方法,也就是公共接口
  • 工厂子类则负责生成具体的产品对象
  • 每次声明一个抽象工厂对象和抽象产品对象,然后用具体工厂来赋值,然后创建具体产品

分析

  • 核心的工厂类将不会再负责所有产品的创建
  • 具体的创建功能交给子类去完成
  • (也可以接受一个参数,一个工厂产生不同产品)
  • 核心工厂类只用给出具体工厂类需要实现的接口,不用负责哪个产品被实例化
  • 这可以允许系统在不修改工厂角色的情况下引进新产品,一个新产品只需要一个具体产品对象以及一个具体工厂对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class PayMethodFactory {
public abstract AbstractPay getPayMethod();
}

public class CashPayFactory extends PayMethodFactory {
public AbstractPay getPayMethod() {
return new CashPay(); }
}

public static void main(){
PayMethodFactory factory;
factory=new CashPayFactory();
AbstractPay payMethod;
payMethod =factory.getPayMethod();
payMethod.pay();
}

实例

Java反射创建举例

1
2
3
4
<?xml version="1.0"?> 
<config>
<className>CashPayFactory</className>
</config>
1
2
3
4
5
PayMethodFactory factory;
AbstractPay payMethod;
factory=(PayMethodFactory)XMLUtil.getBean(); //getBean()的返回类型 为Object,此处需要进行强制类型转换
payMethod =factory.getPayMethod();
payMethod.pay();

日志记录器

image.png

优点

  • 只需要关心产品对应的工厂,甚至都不用关心具体产品类的类名
  • 系统加入新产品的时候,不需要修改抽象工厂和抽象产品提供的接口,只需要增加
  • 工厂创建的细节对用户不可见,工厂决定创建

缺点

  • 类的个数成对增加
  • 引入抽象层,增加了抽象性和理解难度

适用性

  • 一个类可以不知道具体需要的对象的类,只需要知道具体的工厂就好
  • 系统更容易扩展,工厂的子类负责具体的创建,运用多态和里式代换原则,子类对象覆盖父类对象
  • 客户端在使用的时候可以无须关心哪个工厂子类创建产品,需要的时候动态制定,可以将具体工厂的类存储在配置文件或者数据库

抽象工厂

工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象

动机

  • 我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象

新概念

产品等级结构

  • 产品的继承结构,例如电视机是抽象的父类,子类就有小米电视机,华为电视机,TCL电视机
  • 抽象电视机和具体品牌的电视机之间就构成了一个产品等级结构

产品族

  • 产品族就指,同一个工厂生产的,但是处于不同产品等级结构中的一组产品
  • 例如小米电器工厂生产的小米电视,小米手机,小米冰箱
image.png

区别

  • 工厂方法模式针对的是一个产品等级结构,抽象工厂模式需要面对多个产品等级结构
  • 一个具体工厂生产的产品必定是属于同一个产品族的,可以创建出分属于不同的产品等级结构(不同类型)的一个产品族(同一品牌)中的所有对象时
  • 当一个产品族只有一个产品的时候,退化为工厂方法模式
  • 当具体工厂合并,使用统一的工厂中的静态方法来创建产品时,工厂方法模式退化为简单工厂模式

定义

  • 提供一个创建一系列相关或者相互依赖对象的接口,而无需指定具体的类
  • 对象创建型模式

分析

  • 一般包含四个角色,抽象工厂,具体工厂,抽象产品,具体产品
  • 变化的是产品族
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class AbstractFactory {
public abstract AbstractProductA createProductA();
public abstract AbstractProductB createProductB();
}

public class ConcreteFactory1 extends AbstractFactory {
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
}
public class ConcreteFactory2 extends AbstractFactory {
public AbstractProductA createProductA() {
return new ConcreteProductA2();
}
public AbstractProductB createProductB() {
return new ConcreteProductB2();
}
}

实例

image.png image.png

优点

  • 隔离了具体类的生成,客户并不知道什么被创建
  • 所有的具体工厂都实现了抽象工厂中定义的那些公共接口,所以只需要改变具体工厂的实例就可以在某种程度上改变整个软件系统的行为
  • 高内聚,低耦合
  • 当一个产品族的多个对象被设计成一起工作的时候,能够保证客户端始终只是用同一个产品族的对象,因为只使用了一个具体工厂
  • 增加新的产品族和工厂很方便,符合开闭原则

缺点

  • 难以扩展,如果要支持新种类的产品,就代表着对接口的扩展
  • 例如已经有了洗衣机和电视机,增加电脑就意味着对接口进行拓展,在抽象工厂类,所有具体工厂类都需要增加生产新产品的方法,不能很好的支持开闭原则
  • 增加新的工厂以及产品族容易,但是增加新的产品等级结构麻烦

适用性

  • 一个系统并不依赖产品实例如何被创建,组合,和表达的细节
  • 一个系统中有多个产品族,而每次都只使用其中某一个产品族
  • 属于同一个产品族的产品将在一起被使用
  • 系统提供一个产品类的库,所有的产品以同样的接口出现

建造者模式

image-20210624172157351

动机

  • 无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有一系列成员属性,并且有些成员属性还是成员对象,并且在这些负责对象中,还有属性之间的限制条件,如汽车,它包括车轮、 方向盘、发送机等各种部件。
  • 而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车
  • 可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。
  • 用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节也无需关心改复杂对象包含的属性

定义

  • 将一个复杂对象的创建与他的表示分离,使得同样的构建过程可以创建不同的表示
  • 一步一步创建一个复杂的对象,用户只需要指定复杂对象的类型和内容就可以构建他们

结构

  • 抽象建造者Builder
  • 具体建造者ConcreteBuilder
  • 指挥者Director,负责控制产品的生成过程,隔绝了客户与生产过程,针对抽象建造者编程,而客户端只需要知道具体建造者的类型,通过调用Director的setBuilder(…),然后再调用construct()
  • 产品角色Product(包含了多个part)
1
2
3
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Product product = director.construct();

分析

  • 不变的是构建过程
  • 变化的是产品表示,抽象的就是产品的表示

实例

点餐

用户告诉(setMealBuilder)waiter(director)我要套餐1(SubMealBuilderA),然后waiter调用construct,然后依次调用SubMealBuilderA的方法,最后通过getMeal方法,组装成具体的Meal,返回给waiter

image.png

如果Food和Drink都是对象的话,感觉抽象工厂也来了啊

邮件对象

这里的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//由邮件会话对象新建一个邮件消息对象
MimeMessage message=new MimeMessage(session); // 这里是建造者模式的简化版,director和builder结合了
//设置邮件地址
InternetAddress from=new InternetAddress("sunny@test.com"); message.setFrom(from);
//设置发件人
InternetAddress to=new InternetAddress(to_mail);
//设置收件人,并设置其接收类型为TO
message.setRecipient(Message.RecipientType.TO,to);
//设置主题
message.setSubject(to_title);
//设置内容
message.setText(to_content);
//设置发信时间
message.setSentDate(new Date());
//存储邮件信息
message.saveChanges();
Transport transport=session.getTransport("smtp");
transport.connect("smtp.test.com","test","test");
transport.sendMessage(message,message.getAllRecipients());

优点

  • 新产品只用增加新的建造者
  • 使用不同建造者就可以得到不同的产品对象
  • 能够控制产品的创建过程
  • 客户端只用给指挥者传入建造者,调用指挥者的construct方法

缺点

  • 产品都具有较多的共同点,至少在创建流程上需要一样
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体的建造者来实现这种变化,导致系统变得很庞大

对比

  • 建造者返回一个组装好的完整产品,抽象工厂模式返回一系列相关的产品(同一个产品族)
  • 抽象工厂模式中,客户端实例化工厂类,然后点用工厂方法获取需要的产品族;在建造者模式中,客户端可以不直接调用建造者的方法,而是通过director来指导如何生成对象,客户端只是告诉director需要哪个产品
  • 抽象模式就是汽车配件生产工厂,生产一个产品族的各种配件,建造者模式就是汽车组装工厂,通过对配件的组装,可以返回一辆完整的车

原型模式

image-20210624182118396

动机

  • 使用原型模式来复制一个对象自身,从而克隆出多个与原型对象一模一样的对象

  • 有些对象的创建过程比较复杂,这样可以使用原型模式

定义

  • 一种对象创建型模式
  • 用原型实例指定创建对象的种类,并且通过复制这些prototype来创建新的对象
  • 基本工作原理就是通过将一个原型对象传给那个要发动创建的对象,这个对象通过请求原型对象拷贝原型自己来实现创建过程

结构

  • Prototype:抽象原型
  • ConcretePrototype:具体原型
  • Client:客户

分析

  • Java语言中Object对象都有一个clone方法

  • 能够被克隆的原型对象,必须实现接口Cloneable,表示这个对象支持复制

  • 深度克隆表示成员对象也克隆

  • clone()返回的对象满足,与原对象不同,但是属于一个类,并且在定义合理的情况下,equals()也应该返回true

举例

邮件浅克隆

复制邮件的同时不复制附件

image-20210624182835699

优点

  • 简化对象的创建过程,提供了简化的创建结构
  • 动态增加或者减少产品类
  • 可以使用深克隆的方式保存对象的状态

缺点

  • 需要给每一个类配备一个克隆方法
  • 深克隆很复杂
  • 对克隆方法进行改造的时候必须修改源代码

对比

重点在于从已有对象上克隆,模版方法重点在于在父类上修改,精细化,工厂模式在于生产不同的类型的对象

模式举例

  • Spring Bean
  • Control C V

模式拓展

  • 原型管理器
  • 相似对象的复制,即复制后修改属性

单例模式

https://blog.csdn.net/u011386173/article/details/82454714
https://www.kancloud.cn/dengyigegushi/details-dahua/100445

状态模式

动机

  • 一个对象的行为取决于它的状态,这些状态就是一个或者 多个可以动态变化的属性
  • 有状态的对象的状态是可以从实现定义好的一系列值/属性中取出
  • 当对象与外部时间产生互动的时候,内部状态就会发生改变,系统的行为就发生改变(我们希望对象能够与外界交互并发生改变)

定义

  • 当一个对象的内在状态改变的时候,允许改变其行为,这个对象看起来就像是改变了类
  • 状态模式主要解决的是,当控制一个对象状态转换的条件表达式过于复杂的时候(太多的if-else,switch)
  • 可以把状态的判断逻辑转移到表示不同状态的一系列中,可以把复杂的判断逻辑简化
  • 在用户看来,就像是对象自己在运行的时候改变了自己的代码,变化被隐藏
image.png

结构

  • Context环境类,拥有状态,每次调用request,调用自己的状态对象实例的handle(将自己的引用传入)
  • State抽象状态类,可以是抽象类,可以是接口,封装行为
  • ConcreteState具体状态类,用来进行context的状态切换

分析

  • 与策略模式的图,没有一点差别,但是目的不同,状态模式中,向用户保留了透明性
  • 关于状态的转换保留到了具体状态的内部
  • 状态模式描述了对象状态的变换,以及对象如何在每一种变化之下表现出不同的行为
image.png
  • 当然,每个具体状态的handle里面,也可以根据用户的下一个行为,或者context的数据,决定下一个状态:
image.png image.png
  • 但是还是不符合开闭原则,如果需要加一个新的状态,需要修改相关状态的代码,为了获得完全的透明性,付出了代价
  • 策略模式是符合开闭原则的
  • 由于每次切换都要new状态,所以状态对象尽可能小,更好的是,状态本身是没有状态的,也就是说状态对象可以重用

实例

image.png
  • 由行为导致的状态切换→由数据导致的状态切换,前者应该由具体的状态类完成,后者用context类来完成(他知道自己的所有状态
  • 所以,具体的状态对象只放行为以及由行为表示的切换

优点

  • 封装了转换规则
  • 枚举可能的状态,可以方便地增加新的状态
  • 允许状态转换逻辑与状态对象合成一体,而不是一个巨大的条件语句块
  • 可以让多个环境对象共享一个状态对象(状态对象可以重用,静态成员对象),我可以不开心,你也可以同时不开心

缺点

  • 增加系统类和对象的个数
  • 状态模式的结构较为复杂
  • 对开闭原则不友好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责转换的源代码

命令模式

image.png

动机

  • 发送者和接受者完全解耦,两者之间没有任何引用关系,发送请求的对象只需要知道如何发送请求,但是不知道如何完成请求

  • 将不同的receiver的方法调用包装成同一个对象

定义

  • 将一个请求封装为一个对象,从而你可以用不同的请求对客户进行参数化:对请求排队或者记录请求日志,以及支持可以撤销的操作
  • 对象行为型模式

结构

  • Command:抽象命令类
  • Concrete Command:具体命令类,每个命令类有自己对应的receiver(每道菜有对应的厨师)
  • Invoker调用者
  • Receiver接收者
  • Client客户类

分析

  • 请求本身成为了一个对象,发送命令的一方 面向抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接受者关联
  • invoker从client获取具体的command,自己本身只有接口,然后调用command的execute,每个command中有receiver的对象,调用receiver的action(不同的command调用不同的receiver的方法)
  • Client只是负责产生receiver和把command给服务员
  • invoker可以实现一个command队列:
image.png

实例

image.png

Television是receiver

Controller是invoker

image.png

功能键设置,设置这个键是的 功能

通过setCommand指定功能,然后下次点击的时候,调用execute,然后就调用指定的命令实例的execute,对receiver执行不同的操作

image.png

优点

  • 降低耦合度
  • 新命令的添加
  • 设计命令队列和宏命令(组合命令,命令模式和组合模式联用,notifyAll,foreach)
  • 方便实现Undo和Redo(调用一个完全相反的方法抵消行为,redo就是传入新的command覆盖)

缺点

  • 导致某些系统有过多的具体命令类,因为一个命令就是一个类

适用性

  • 需要将请求者invoker和接收者解耦,使得不直接交互
  • 系统需要在不同的时间,制定请求,请求排队和执行请求
  • 支持命令的撤销和恢复操作
  • 一组操作结合在一起

应用

Java事件委派

  • 页面组件发送请求,AWT提供的事件监听器接口是抽象命令接口,用户自己写具体命令实现事件处理
  • 具体命令类中,可以调用业务处理的方法来执行命令‘

撤销操作的实现

image-20210626224548989

宏命令

image-20210626224926146

观察者模式

模式动机

  • 建立一种对象与对象之间的依赖关系

  • 一个对象发生改变时将自动通知其他对象其他对象将相应做出反应。

  • 在此, 发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,

  • 而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展

定义

  • 发布-定义模式,模型-视图模式,源-监听器模式,从属者模式,是一种对象行为模式
  • 每当被观察对象的状态发生改变的时候,相关的依赖对象都能得到通知并且被自动更新

结构

image.png image.png
  • 目标,具体目标,观察者,具体观察者
  • 观察者使用update方法接收传递过来的数据,必须持有subject的引用,subject引用 方便detach以及pull模式
  • 被观察者维持了观察者的列表(一个反向的引用),发生改动过后就遍历,notify,通知时并不需要知道谁是它的观察者, 可以有任意数目的观察者订阅它并接收通知,观察者也不知道其他观察者的存在
  • 使用抽象的观察者(observer)和被观察者(subject),减少具体类的耦,不同具体类可以重写具体的通知,接收通知方法

实例解析

猫叫,老鼠狗跑

image.png

自定义登陆

如果LoginEvent发生了,那么就通知给Listener,给他们传入自己的引用,然后让他们进行validate

image.png

优点

  • 表示层和数据逻辑层的分离,定义了隐式的信息更新传递机制,可以有各种各样的不同的表示层作为观察者
  • 符合开闭原则
  • 支持广播
  • 目标和观察者之间是抽象的耦合

缺点

  • 通知很多观察者会花费很多时间
  • 循环依赖会触发循环调用
  • 观察者并不能知道目标是如何变化的,只知道发生了变化

适用性

  • 一个对象的改变导致多个对象的改变,并且不知道具体的数量
  • 一个对象必须通知其他对象,尽管不知道这些对象是谁
  • 系统中创建一个触发链

拓展

Java包

image-20210626230449799

MVC

image-20210626230514112

中介者模式

动机

  • 系统结构复杂:对象之间存在大量的关联和相互调用,如果有一个对象发生变化,需要跟踪和该对象关联的所有对象,进行适当的处理,系统结构复杂
  • 可重用性差:由于一个对象和其他对象具有很强的关联,若没有其他对象的支持,一个对象很难被另一个系统或模块重用,这些对象表现出来更像 一个不可分割的整体,职责较为混乱。
  • 系统扩展性低:增加一个新的对象需要在原有相关对象上增加引用,增加新的引用关系也需要调整原有对象,系统耦合度很高,对象操作很不灵活,扩展性差。

定义

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

结构

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
28
29
30
31
32
public abstract class Mediator {
protected ArrayList colleagues;
public void register(Colleague colleague) {
colleagues.add(colleague);
}
public abstract void operation();
}
public class ConcreteMediator extends Mediator{
publiv void operation(){
((Colleague)(colleagues.get(0))).method1();
}
}
public abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator=mediator;
}
public abstract void method1();
public abstract void method2();
}
public class ConcreteColleague extends Colleague {
public ConcreteColleague(Mediator mediator) {
super(mediator);
}
public void method1() {
......
}
public void method2() {
mediator.operation1();
}
}

分析

  • 每个colleague想要给其他colleague进行通信的时候,所有的消息通过中介者进行传递
  • 使得对象之间的关系数量急剧减少
  • 中介者需要中转和协调,中转属于结构性的作用,协调属于行为性的作用,具体中介者需要知道所有具体同事类
  • 通过创造出一个中介者对象,将系统中有关的对象索引用的其他对象数目减少到最小,使得一个对象与其同事对象之间的交互作用被对象与中介者之间的相互作用所替代,迪米特法则

实例

ChatGroup作为中介者,保存了所有成员的列表

成员自身也有聊天室的引用

钻石会员可以发送图片

聊天室在执行的时候也会对发送内容进行过滤

image.png

优点

  • 解耦,简化了对象的交互
  • 简化同事类之间的设计和实现

缺点

  • 中介类包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护

适用性

  • 对象之间存在复杂的引用关系,并且一个对象引用了其他很多对象并且直接和他们通信
  • 通过一个中间类来封装多个类中的行为,并且不想生成太多的子类
  • 迪米特法则的典型应用(最小知识法则)

模版方法模式

动机

  • 是基于继承的代码复用

  • 面向对象设计的核心之一。

  • 在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中。

定义

  • 定义一个算法的骨架,将一些步骤延迟到子类中,子类可以不改变一个算法的结构就可以重定义该算法的特定步骤
  • 类行为型模式

分析

  • 只有类之间的继承关系,没有对象关联关系
  • 抽象类设计师可以给出一个算法的轮廓和骨架,另一个设计是可以负责给出算法的逻辑步骤,实现逻辑步骤的方法是基本方法,汇总起来后被称为模版方法
  • 基本子类的基本方法可以覆盖父类中定义的基本方法,子类中的钩子方法(例如条件判断方法)可以覆盖父类的钩子方法,从而可以通过在子类中实现的钩子方法对父类方法的执行进行约束,实现子类对于父类行为的反向控制
image.png image.png image.png

模式结构

image-20210627130936235

实例

银行办理业务

实现了不同的transact

image-20210627131134394

数据库操作的模版

image-20210627131241470

优点

  • 代码复用的基本技术
  • 在一个类中抽象地定义算法,子类中实现对细节的处理
  • 反向的控制结构,通过父类调用其子类的操作,通过对子类的拓展添加新的行为,钩子方法使得子类可以控制父类的行为

缺点

  • 不同的实现都需要定义一个子类,导致类的个数增加,但是符合单一职责原则

适用性

  • 一次性实现一个算法的不变的部分,并将可变的行为留给自类
  • 公共的行为被提取出来并且集中到一个公共父类中

拓展

好莱坞原则

子类不需要调用父类而是通过父类的方法来调用子类

勾子方法

为了对其他方法进行约束,通常返回一个布尔类型

适配器模式

想想电源适配器

模式动机

  • 服务端升级导致的接口不一致的问题,遵循开闭原则又不想改变客户端的原有代码
  • 现有的接口需要转化为客户类期望的接口,保证了对现有类的重用
  • 适配器模式中,定义一个包装类,叫做适配器Adapter,被包装的类叫做适配者(Adaptee)
  • 适配器的作用就是把客户端的请求转发为对适配者的相应接口的调用
  • 客户并不直接访问适配者类,但是是对客户端透明的
  • 既可以通过对象组合,也可以通过类的继承来实现

模式结构

  • 客户端调用request,然后adapter是继承或者实现Target接口,request实际上调用的adaptee的specificRequest
  • 客户端本身不是这个模式的一部分
  • 下面是对象适配器,左边是继承,右边是依赖
image.png
1
2
3
4
5
6
7
8
9
public class Adapter extends Target{
private Adaptee adaptee;
public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
public void request(){
adaptee.specificRequest();
}
}
  • 下面是类适配器,左边是接口实现,右边是继承
  • adapter在request中直接调用继承下来的specificRequest,好处在于在类的继承的时候是白盒继承的,适配的过程中,是可以对其中的一些方法进行重写,修改
  • 适配器很多时候是进行模块组合的,对类的新的部分进行修改
image.png
1
2
3
4
5
public class Adapter extends Adaptee implements Target {
public void request() { // this method is declared in Target
specificRequest(); // this method is declared in Adaptee
}
}

实例分析

仿生机器人

image.png

加密适配器

对象适配器,不同的Adapter针对于不同的Adaptee

image.png

优点缺点

  • 目标类和适配者类解耦
  • 增加了类的透明性和复用性
  • 灵活性和拓展性很好
  • 类适配器中,可以通过继承置换一些方法,更加灵活,但是有些语言不能多重继承,一个适配器只能针对一个适配者
  • 对象适配器可以一次性适配多个适配者,但是想要替换适配者类的方法就不容易,只能再做一个适配者的子类,替换掉适配者的方法,然后将子类作为真正的适配者Adaptee subAdaptee = new SubAdaptee();

应用

JDBC

  • JDBC给出一个客户端的通用接口,具体的数据库的JDBC驱动就是一个适配器

模式拓展

默认适配器

  • 抽象类实现接口,每个方法有一个默认实现,哦那个方法,抽象类的子类可以选择的覆盖某些方法
  • 单接口适配器模式

双向适配器

  • 对象模式:持有对Target和Adaptee的引用
  • 问题:如何使用类模式
image.png

组合模式

更像是一个容器
将对象组合成树形结构以表示 部分-整体 的层次结构,组合模式使得用户对单个对象以及组合对象的使用具有一致性

模式动机

  • 对于树形结构(例如文件夹),当容器对象的某一个方法被调用的时候,将会遍历整个树形结构,寻找也包含这个方法的成员对象(可以是子容器,也可以是叶子对象),并且调用执行
  • 客户端希望能够有区别的对待容器对象和叶子对象,但是希望一致的处理,而不需要去具体考虑
  • 组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用的时候无须对他们进行区分,可以一致的处理他们

定义

  • 可以称为 整体-部分 模式,属于对象的结构模式,将对象组织到树结构中,可以用来描述整体与部分的关系。

模式结构

image.png
  • Component 抽象构件:为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。聚合组成了容器构件
  • Leaf 叶子构件:在组合结构中表示叶子节点对象,叶子节点没有子节点
  • Composite 容器构件:在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为
  • Client 客户类
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
public 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();
}
public class Leaf extends Component {
public void add(Component c) {
//异常处理或错误提示
}
public void remove(Component c) {
//异常处理或错误提示
}
public Component getChild(int i) {
//异常处理或错误提示
}
public void operation()
{
//实现代码
}
}
public class Composite extends Component {
private ArrayList list = new ArrayList();
public void add(Component c) {
list.add(c);
}
public void remove(Component c) {
list.remove(c);
}
public Component getChild(int i) {
(Component)list.get(i);
}
public void operation() {
for(Object obj:list) {
((Component)obj).operation();
}
}
}

分析

  • 定义了一个抽象构件类,既可以代表叶子,又可以代表容器,客户端是针对这个接口进行编程的,不需要知道他是叶子还是容器
  • 容器对象和抽象构件类之间还建立了一个聚合关联关系(看那个空菱形的线),在容器对象中包含多个抽象构件,即既可以包含叶子,又可以包含容器
image.png

优点

  • 层次清楚,调用简单,树形结构

缺点

  • 设计抽象,并不是所有的方法都是与叶子对象类有关系的

拓展

透明组合模式

image-20210627144139642

安全组合模式

image-20210627144212589

桥接模式

模式动机

设想如果要绘制矩形、圆形、椭圆、正方形,我们至 少需要4个形状类,但是如果绘制的图形需要具有不 同的颜色,如红色、绿色、蓝色等

image.png
  • 方案一:每个形状都提供一套各种颜色的版本(违反了单一职责)
  • 方案二:根据实际情况对形状和颜色进行组合,将继承关系转换为关联关系,组合优于继承,从而降低了类与类之间的耦合,减少了代码编写量
  • 对于有两个变化纬度的系统

模式结构

image.png
  • Abstraction 抽象类
  • RefinedAbstraction 扩充抽象类,可以有多个
  • Implementor 实现类接口
  • ConcreteImplementor 具体实现类,可以有多个

分析

  • 实现上是一样,思想不一样,与策略模式的区别就在于桥接模式的目的就在于创建类型,减少类,但是能有同样多的类型使用,每一种类型都要出现

实例

一个维度是继承

另一个维度是聚合+接口

image.png image.png

优点

  • 符合开闭原则
  • 可扩展性,在已有维度中任意拓展都不需要修改原有系统
  • 可以向用户隐藏实现细节

缺点

  • 理解与设计难度,需要针对抽象进行设计
  • 设计的时候需要识别出两个独立变化的维度

模式应用

Java虚拟机

image-20210627145105201

AWT GUI

GUI构件在获得操作系统的类型过后,自动配置对应的display

JDBC

JDBC驱动程序可以动态的将一个特定类型的数据库与一个Java应用程序绑定在一起;

使用JDBC的应用系统就是抽象角色,使用的数据库就是实现角色(不同的数据库,而不同的数据库使用相同JDBC作为适配器)

模式拓展

与适配器模式的联动

  • 桥接模式用于系统的初步设计,独立存在的两个纬度的类,可以将其分别抽象化和实现化两个角色,可以分别进行变化
  • 但是在设计完成后,系统与已有类无法协同工作,就可以采用适配器模式,特别是那些涉及到大量第三方应用接口的情况
image.png

装饰模式

模式动机

  • 一般有两个方式给一个类或者对象,添加行为
  • 继承机制:子类拓展和使用父类的方法
  • 关联机制:将一个类的对象嵌入到另一个对象中,由另一个对象来决定是否调用嵌入对象的行为
  • 用不用装饰者取决于添加行为的方式是不是不确定的
  • 装饰者以对客户透明的方式,动态地给对象附加上更多的责任,客户端并不知道对象在装饰前和装饰后有什么不同。装饰者模式可以在不需要创建更多子类的情况下,将对象的功能拓展。

定义

  • 动态地给一个对象添加一些额外的职责
  • 与适配器的别名相同,但是适用于不同的场合

结构

image.png
  • 抽象构件:聚合到抽象装饰类
  • 具体构件:不能去掉,否则右边成了递归,没有对于抽象构件的引用
  • 抽象装饰类:继承了抽象构件¥
  • 具体装饰类:增加职责可以通过增加状态,也可以通过增加行为,不能破坏里氏代换原则,那个addBehavior应该是-

分析

image.png image.png

在操作了父类(Component)的operation过后,再执行新增的方法

实例

变形金刚

让车能够变身为机器人或者飞机,以进行说话和飞行

image.png

多重加密系统

给SimpleCipher添加多种行为,添加不同的行为

image.png

优点

  • 动态拓展行为,可以选择不同的装饰器,不同的行为
  • 装饰类的不同的排列组合可以得到不同功能的对象
  • 符合开闭原则,根据需要添加新的构件类和装饰类

缺点

  • 不受控制,比继承更加灵活的特性让装饰模式比继承更加容易出错,经过多次装饰的对象,排错也很困难
  • 产生很多小对象,产生很多具体装饰类

应用

JavaIO

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
28
public class FileInputStream extends InputStream{
...
protected FileInputStream(String path){
...
}
}
public class FilterInputStream extends InputStream{
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
protected read(byte[] data){}
}
public class BufferedInputStream extends FilterInputStream{
protected BufferedInputStream(InputSream in)
{
super(in);
}
protected read(byte[] data){
super.read(data);
}
}
....
FileInputStream inFS = new FileInputStream("temp/fileSrc.txt");
BufferedInputStream inBS = new BufferedInputStream(inFS); //定义一个字节数组,用于存放缓冲数据
byte[] data = new byte[1024];
inBS.read(data);

拓展

简化

image-20210627153308009

透明装饰

  • 面向抽象编程,不能声明具体构件类型和具体装饰类型,应该全部声明为抽象构件类型
1
2
3
4
Cipher sc,cc,ac;
sc=new SimpleCipher();
cc=new ComplexCipher(sc);
ac=new AdvancedCipher(cc);

半透明

  • 允许声明具体装饰者类型的对象
1
2
3
4
5
Transform camaro;
camaro=new Car();
camaro.move();
Robot bumblebee=new Robot(camaro); bumblebee.move();
bumblebee.say();

外观模式

动机

引入外观角色过后,用户只需要与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度

image.png

定义

  • 外部与子系统之间的通信必须通过一个统一的接口
  • 使得子系统更加容易使用
  • 门面模式,一种对象结构型模式

结构

image.png
  • facade:外观角色
  • subsystem:子系统角色
1
2
3
4
5
6
7
8
9
10
11
public 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();
}
}

分析

  • 单一职责原则的体现,一个系统被划分为若干个子系统,有利于降低整个系统的复杂性
  • 达到这个目标的途径之一,就是引入一个外观对象,为子系统的访问提供了一个简单并且单一的入口
  • 迪米特法则的体现,降低系统复杂度以及降低客户类与子系统类的耦合度

实例

电源开关

image.png

文件加密

先用reader读,在用cipher加密,在用writer卸乳

image.png

优点

  • 减少客户处理的对象数目,客户代码简单
  • 子系统的组件变化不会影响客户
  • 子系统的修改对其他子系统没有影响,内部变化也不会影响外观对象

缺点

  • 增加新的子系统可能会违背开闭原则

应用

JDBC数据库操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public class JDBCFacade {
private Connection conn=null;
private Statement statement=null;
public void open(String driver,String jdbcUrl,String userName,String userPwd) {
......
}
public int executeUpdate(String sql) {
......
}
public ResultSet executeQuery(String sql) {
......
}
public void close() {
......
}
}

Session外观模式

image-20210628144529050

拓展

单例模式+多个不同的外观类

一个外观类一个实例,但是可以涉及多个外观类,每个外观类负责和一些特定的子系统交互

不要用外观类添加新行为

新的行为通过修改原有子系统类或者增加新的子系统类来实现

迪米特法则

代码重构达到了迪米特法则的要求,使得客户端与子系统内部的对象的相互作用被外观对象所取代

抽象外观类

  • 客户端针对抽象外观类进行编程,新的业务需求增加新的外观类,修改配置文件来更换外观类
image-20210627155232694

享元模式

模式动机

  • 对象太多的时候,运行代价过高,带来性能下降的问题
  • 享元模式通过共享技术实现相同或相似对象的重用
  • 共享的相同内容称为内部状态
  • 需要外部环境来设置的,不能共享的内容称为外部状态,因此可以通过设置不同的外部状态使得相同的对象可以拥有一些不同的特征
  • 通常会出现工厂模式,需要创建一个享元工厂来负责为一个享元池
  • 能够共享的内部状态是有限的,所包含的内部状态较少,称为细粒度对象,享元模式的目的就是使用共享技术来实现大量细粒度对象的复用

模式定义

  • 使用共享技术,有效的支持大量细粒度对象的复用,系统只使用少量的对象,实现对象的多次复用
  • 轻量级模式
  • 对象结构形模式

模式结构

image-20210627155452566
  • 抽象享元类
  • 具体享元类:内部状态作为成员属性,可以共享,可以有多个对象
  • 非共享具体享元类:不可以共享
  • 享元工厂类:提供一个存储享元对象的池,需要享元对象的时候就从里面获取,如果没有池就创建新的对象给用户,并保存这个对象

模式分析

  • 共享的关键是区分内部状态和外部状态
  • 内部状态可以共享,不会改变
  • 外部状态可以随环境改变,不可以共享

实例

网络设备

image-20210627161033660

共享网络设备

多台计算机共享一个网络设备,但必须使用不同的端口

image-20210627161122942

优点

  • 极大减少内存中对象的数量
  • 享元对象可以在不同的环境中被共享

缺点

  • 需要分离出内部状态和外部状态
  • 读取外部状态运行时间更长

适用

  • 大量相同或者相似的对象,大部分状态都可以外部化
  • 多次重复使用享元对象

模式拓展

单纯享元模式

所有享元对象都是可以共享的,不存在非共享具体享元类,参考上面的网络设备

复合享元模式

单纯享元使用组合模式形成复合享元

image-20210627162825047

对比原型模式

image-20210624182118396

代理模式

动机

  • 通过引入一个新的对象来实现对真实对象的操作,或者将这个新的对象作为真实对象的替身,来间接访问真实对象
  • 客户不能使用真正的对象,外观模式是可以访问真正的对象的

定义

  • 给某一个对象提供一个代理Proxy,并由代理对象控制对原对象的引用

结构

image.png
  • Subject 抽象主题角色
  • Proxy 代理主题角色,也是Subject,包含了对真正对象的应用
  • RealSubject

实例

论坛权限控制

image-20210627163100286

数学运算

image-20210627163315521

优点

  • 协调调用者和被调用者,降低系统耦合度
  • 远程代理远程访问
  • 虚拟代理减少消耗
  • 保护代理控制权限

缺点

  • 处理速度变慢
  • 额外的工作

适用

  • 远程代理
  • 虚拟代理,内存节省技术,例如对大图浏览的控制,多线程,先小图
  • Copy-on-Write代理,延迟操作