23种设计模式整理
2023-12-03 13:45:16 0 举报
AI智能生成
23种设计模式整理,并附加了相关的案例
作者其他创作
大纲/内容
概念
创建者模式对比
工厂方法 VS 建造者
工厂注重的是整体对象的创建
建造者注重的是部件的构建过程
抽象工厂 VS 建造者
抽象工厂实现对产品家族的创建,一个产品家族具有不同维度的组合,不需要关注构建过程,只关心由什么工厂生产即可
建造者是按照指定的蓝图构造产品,主要是通过组装零件而产生一个新产品
结构型模式
描述如何将类或对象按照某种布局组成更大的结构
分为类结构模型模式和对象结构型模式
类结构型模式采用继承机制来组织接口和类
对象结构型模式采用组合或聚合来组合对象
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性
举例
中介和代理商
行为型模式
用于描述程序在运行时复杂的流程控制
描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它设计算法与对象之间职责的分配
行为型模式分为 类行为型模式 和 对象行为型模式
类行为型模式采用继承机制来在类之间分配行为
对象行为型模式采用组合或聚合在对象间分配行为
由于组合或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以后者要比前者有更大的灵活性
创建型模式
单例模式
特点
私有构造----只能自己创建
提供一个外界访问的获取对象的方法
实现
饿汉 : 不管用不用,先创建
懒汉 : 用的时候再创建
线程安全问题
double check 优化
静态内部类的方式
枚举方式(饿汉)---枚举类型是线程安全的,且只会装载一次
原型模式
概念:用一个已经创建的实例作为原型,通过复制的方式来创建一个和原型相同的新对象
角色
抽象原型类:规定了具体原型对象必须实现的clone() 方法
具体原型类:实现抽象原型类的clone() 它是可以被复制的对象
访问类:使用具体原型中的clone(), 来复制新的对象
实现
浅克隆:克隆的对象如果属性中的引用类型和原型指向的同意地址(Cloneable 接口)
深克隆:使用对象流来实现 或 者通过json字符串来处理 序列化和反序列化
建造者模式
概念:将一个对象的构建和表示分离,是的同样的构建过程可以创建不同的表示
使用场景:
适合某个对象的构建过程很复杂的情况,由多个部件构成,同时产品之间要有相似性
核心:构建和装配解耦,实现了构建算法、装配算法的解耦,实现了更好的复用
不同的构建器,相同的装配,可以做出不同的对象
相同的构建器,不同的装配顺序,也可以做出不同过的对象
角色
抽象建造者(Builder):这个接口/抽象类规定要实现复杂对象的哪些部分需要创建,不涉及具体的对象部件的创建
具体建造者类(ConcreteBuilder):实现Builder,完成复杂产品的各个部件的具体创建,构造过程完成后,提供产品的实例
产品类(Product):需要创建的复杂产品
指挥类(Director):调用具体创建者来创建复杂对象的各个部分,指导者中不涉及具体产品的信息,只负责保证对象各部分的完整创建或按顺序创建
案例
需求:创建贡献单车,自行车是一个复杂的对象,包含了车架、车座等组件的生产,而车架又有碳纤维,铝合金等材质,车座有橡胶的、真皮的,所以是一个复杂的创建过程,可以使用建造者模式来创建
说明:Bike是产品,包含车架、车座等组件 Builder是抽象建造者,MobekeBuilder和OfoBuilder是具体的建造者,Director是指挥者
代码
原始
Bike
public class Bike {
private String frame; //车架
private String seat; // 车座
public Bike(String frame, String seat) {
this.frame = frame;
this.seat = seat;
}
//get set 省略
}
private String frame; //车架
private String seat; // 车座
public Bike(String frame, String seat) {
this.frame = frame;
this.seat = seat;
}
//get set 省略
}
Builder
public abstract class Builder {
// 声明Bike类型的变量,并进行赋值
protected Bike bike = new Bike();
public abstract void builderFrame();
public abstract void builderSeat();
public abstract Bike createBike();
// 将指挥者中的逻辑放到Builder中,这样就可以省去指挥者类了,可以简化
// 这样Builder 中的职责就不单一了,有利也有弊端
/*public Bike construct(){
this.builderFrame();
this.builderSeat();
return this.createBike();
}*/
}
// 声明Bike类型的变量,并进行赋值
protected Bike bike = new Bike();
public abstract void builderFrame();
public abstract void builderSeat();
public abstract Bike createBike();
// 将指挥者中的逻辑放到Builder中,这样就可以省去指挥者类了,可以简化
// 这样Builder 中的职责就不单一了,有利也有弊端
/*public Bike construct(){
this.builderFrame();
this.builderSeat();
return this.createBike();
}*/
}
MobikeBuilder
public class MobikeBuilder extends Builder {
@Override
public void builderFrame() { bike.setFrame("碳纤维车架"); }
@Override
public void builderSeat() { bike.setSeat("真皮车座"); }
@Override
public Bike createBike() { return bike; }
}
@Override
public void builderFrame() { bike.setFrame("碳纤维车架"); }
@Override
public void builderSeat() { bike.setSeat("真皮车座"); }
@Override
public Bike createBike() { return bike; }
}
Director
public class Director {
private Builder builder; // 声明Builder类型的变量
private Director(Builder builder) { this.builder = builder; }
// 组装自行车的功能
public Bike construct(){
builder.builderFrame();
builder.builderSeat();
return builder.createBike();
}
// 模拟客户端测试
public static void main(String[] args) {
Director director = new Director(new MobikeBuilder());
Bike bike = director.construct();
System.out.println(bike.getFrame()+" " + bike.getSeat());
}
}
private Builder builder; // 声明Builder类型的变量
private Director(Builder builder) { this.builder = builder; }
// 组装自行车的功能
public Bike construct(){
builder.builderFrame();
builder.builderSeat();
return builder.createBike();
}
// 模拟客户端测试
public static void main(String[] args) {
Director director = new Director(new MobikeBuilder());
Bike bike = director.construct();
System.out.println(bike.getFrame()+" " + bike.getSeat());
}
}
OfoBuilder:参考上面
扩展
场景
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,
就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时可以利用建造者模式进行重构
代码
public class Phone {
private String cpu;
private String screen;
private String memery;
private String mainboard;
// 私有构造方法,不允许 new 的方式创建
private Phone(Builder builder) {
this.cpu = builder.cpu;
this.screen = builder.screen;
this.memery = builder.memery;
this.mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memery;
private String mainboard;
public Builder setCpu(String cpu) {
this.cpu = cpu;
return this;
}
// screen memery mainboard 参考上面写法
// 构建对象
public Phone build(){
return new Phone(this);
}
}
// 测试
public static void main(String[] args) {
Phone p = new Builder()
.setCpu("cpu")
.setScreen("screen")
.setMemery("memery")
.setMainboard("mainboard")
.build();
System.out.println(p.cpu);
}
}
private String cpu;
private String screen;
private String memery;
private String mainboard;
// 私有构造方法,不允许 new 的方式创建
private Phone(Builder builder) {
this.cpu = builder.cpu;
this.screen = builder.screen;
this.memery = builder.memery;
this.mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memery;
private String mainboard;
public Builder setCpu(String cpu) {
this.cpu = cpu;
return this;
}
// screen memery mainboard 参考上面写法
// 构建对象
public Phone build(){
return new Phone(this);
}
}
// 测试
public static void main(String[] args) {
Phone p = new Builder()
.setCpu("cpu")
.setScreen("screen")
.setMemery("memery")
.setMainboard("mainboard")
.build();
System.out.println(p.cpu);
}
}
缺点
建造者模式创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合,因此有一定的局限性
工厂模式
简单工厂模式
具体产品根据条件来通过一个工厂类创建
角色
抽象父类 (Coffee)
子类实现父类 (AmericaCofee extends Coffee)
通过传入参数的形式,由工厂类创建具体的实现类 (一般是由if else 根据参数判断具体创建哪个) Factory.createCoffee(coffeeType) )
可以通过配置文件中的方式来创建 配置文件中 key=value key就是需要传入的参数 value 可以设置为类名的全类名
工厂方法模式
具体产品由具体工厂创建
完全遵循开闭原则
角色
抽象产品 : 定义产品规范,描述产品特性
具体产品 : 实现抽象产品所定义的接口, 由具体的工厂来创建
抽象工厂 : 提供创建产品的接口,调用者通过访问具体工厂的工厂方法来创建产品
具体工厂 : 是按抽象工厂中的抽象方法,完成具体产品的创建
抽象工厂模式
角色
抽象产品 :
具体产品 :
抽象工厂 : 提供创建产品的接口, 它包含多哥创建产品的方法,可以创建不同等级的产品
具体工厂 : 实现抽象工厂中多个产品的创建方法,完成具体产品的创建
总结
简单工厂 : 不符合开闭原则
工厂方法 : 符合开闭原则, 但是具体产品要用具体的工厂创建,这样如果产品很多,就会出现类爆炸,不方便维护 并且只适合创建同类产品
抽象工厂 : 里面定义了多个创建产品的方法, 可以生产同一产品族的不同产品 例如换输入法皮肤等
场景:Collection.iterator 底层就用到了工厂模式
结构型模式
代理模式(Proxy)
概述
由于某些原因需要给某个对象提供一个代理以控制对该对象的访问。这时访问对象不适合或不能直接引用目标对象,代理对象作为访问对象和目标对象的中介
角色
抽象主题(Subject)类
通过接口或抽象类声明真实主题和代理对象实现的业务方法
真实主题(Real Subject)类
实现抽象主题中的具体业务,是代理对象所代表的真实对象、是最终要引用的对象
代理(Proxy)类
提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
代理模式的具体实现
静态代理
在类编译期就生成
示例
抽象主题
public interface SellTickets {
void sell();
}
void sell();
}
真实主题
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("火车站卖票");
}
}
代理类
public class ProxyPoint implements SellTickets {
// 声明火车站
private TrainStation trainStation = new TrainStation();
@Override
public void sell() {
// 增强逻辑 ---- 收取服务费
System.out.println("----收取服务费");
// 卖票
trainStation.sell();
}
public static void main(String[] args) {
new ProxyPoint().sell();
}
}
// 声明火车站
private TrainStation trainStation = new TrainStation();
@Override
public void sell() {
// 增强逻辑 ---- 收取服务费
System.out.println("----收取服务费");
// 卖票
trainStation.sell();
}
public static void main(String[] args) {
new ProxyPoint().sell();
}
}
动态代理
在JAVA运行时生成、JAVA中分为JDK代理和CGLIB代理
JDK代理(必须定义接口,对接口进行代理)--- 代理类实现了目标接口
java中提供了一个动态代理类Proxy,Proxy并不是上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取对象
示例
子主题
代售点代理火车站卖票
抽象主题和目标对象参考静态代理中的逻辑(相同)
代理对象的获取
原理
动态生成的字节码
过程
1、动态生成字节码(不产生文件、只在内存),如上图
2、在生成的接口方法实现中,调用我们自己定义的InvocationHandler子实现类中的invoke 完成增强
3、2中的invoke实际上是调用的 ProxyFactory 中我们自己new的InvocationHandler中的invoke方法
CGLIB代理(为没实现接口的类进行代理)--- 代理类是目标类的子类
示例
TrainStation
ProxyFactory
原理
底层采用ASM字节码生成框架,使用字节码技术生成代理类
CGLIB原理是动态生成被代理的子类——所以CGLIB不能对声明为final的类或者方法进行代理
三种模式的对比
JDK代理和CGLIB代理
有接口使用JDK/CGLBI代理,无接口使用CGLIB代理
动态地理和静态代理
动态代理最大的好处是接口中声明的所有方法都被转移到调用处理器的一个集中方法中处理、这样修改的时候,修改成本较少
适配器模式(Adapter)
区分
类适配器模式(继承方式实现)
耦合度比较高,且要求程序员对内部组件结构要了解,所以使用较少
对象适配器模式(组合、聚合的方式)
角色
目标(Target)接口
当前系统业务所期待的接口,可以是抽象类,也可以是接口
适配者(Adaptee)类
被访问者和适配器的现存组件库中的组件接口
适配器(Adapter)类
转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的访问方式访问适配者
实现
类适配器模式(了解)
背景
读卡器案例,一台只能读取SD卡的计算机,读取TF中的内容,就需要适配器
实现
目标类
适配者类
适配器类
调用者类
对象适配器模式
实现方式:对象适配器模式可采用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口
实现
目标类
同上
适配者类
同上
适配器类
调用类
装饰者模式
举例:快餐店,
快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜这些是要额外加钱,每个配菜价钱筒仓都不一样,所以计算总价就会比较麻烦,这种场景就适合使用装饰者模式
继承方式实现的缺点:扩展性不好,会出现类爆炸的情况
角色
抽象构建(Component)角色
定义一个抽象接口以规范准备接收附加责任的对象
具体构建(ConcreteComponent)角色
实现抽象构建,通过装饰角色为其添加一些职责
抽象装饰(Decorator)角色
继承或实现抽象构建,并包含具体构建的实例,可通过其子类扩展具体构建的功能
具体装饰(ConcreteDecorator)角色
实现抽象装饰者的方法,并给具体构建对象添加附加责任
案例(快餐店案例)
用鸡蛋等配料来装饰快餐,来计算价格
示例
抽象构建角色:快餐类
抽象装饰者角色:装饰者类
具体构建角色:炒饭类
具体构建角色:炒面类
参考 炒饭类
具体装饰者角色:配料Egg类
具体装饰者角色:配料Bacon类
参考Egg
Client 测试类
子主题
测试结果
/* 结果
炒饭 10.0元
-------------------------------------------------
鸡蛋炒饭 11.0元
-------------------------------------------------
培根鸡蛋炒饭 13.0元
-------------------------------------------------
培根培根鸡蛋炒饭 15.0元
*/
炒饭 10.0元
-------------------------------------------------
鸡蛋炒饭 11.0元
-------------------------------------------------
培根鸡蛋炒饭 13.0元
-------------------------------------------------
培根培根鸡蛋炒饭 15.0元
*/
好处
比继承更加灵活性的扩展,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化结果,装饰者类可以和被装饰者独立发展,不会相互耦合
使用场景
不能采用继承的方式或通过继承不利于扩展系统时
不能采用继承的情况
1、系统中存在大量的独立扩展,为支持每一种组合将会产生大量的子类,使得子类数目呈爆炸式增长的情况
2、因为类的定义不能继承的情况,如:final
不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
当对象的功能要求可以动态地添加,也可以动态地撤销时
案例场景
IO流中的包装类使用到了装饰着模式、如 BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
桥接模式
概述
定义
将抽象和实现分离,使他们可以独立变化,它是用组合关系替代继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度
角色
抽象化(Abstraction)角色
定义抽象类,并包含一个对实现话对象的引用
扩展抽象化(Refined Abstraction)角色
是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
实现化(Implementor)角色
定义实现化角色的接口,供扩展抽象化角色调用
具体实现化(ConcretImplementor)角色
给出实现化角色接口的具体实现
案例
需求
子主题
实现
实现化角色:视频文件接口
子主题
具体实现化角色:AVI文件类
子主题
具体实现化角色:RMVB文件类
参考AVI文件类
抽象化角色:抽象的操作系统类
子主题
具体的抽象化角色:windows os 类
子主题
具体的抽象化角色:mac os 类
参考windows 类
测试类:Client
子主题
使用场景
1、当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时
2、当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加的时
3、当一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性时,避免两个层次间建立静态的继承关系,可通过桥接模式在抽象层建立关系
外观模式(门面模式)
定义
是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式,该模式对外有一个统一的接口,外部应用不用关心内部子系统的具体实现细节
角色
外观(Facade)角色
为多个子系统提供一个共同的接口
子系统(Sub System)角色
实现系统的部分功能,客户可以通过外观角色访问它
案例
智能家电控制
实现
子系统:TV
子系统:Light
参考TV
子系统:AirCondition
参考TV
外观类:SmartAppliancesFacade
测试类:Client
优缺点
优点
降低了子系统和Client间的耦合度,使得子系统的变化不会影响调用它的客户类
对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易
缺点
不符合开闭原则,修改麻烦
使用场景
分层结构系统构建时,使用外观定义入口
复杂系统设计时,外观模式可以提供一个简单的入口
Client和多个子系统存在很大的联系的情况,可以引入外观模式将它们分离
组合模式(部分整体模式)
定义
用于把一组相似的对象当作一个单一的对象
组合模式依据树形结构来组合对象,用来表示部分以及整体层次,这种类型的设计结构属于结构型模式,它创建了对象组的树形结构
例如Windows系统中的文件和文件夹的这种结构,对于用户来说文件夹的操作和文件的操作基本一致,但是对于系统来说有很大差别
角色
抽象根节点(Component)
定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性
树枝节点(Composite)
定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构
叶子节点(Leaf)
叶子节点对象,其下再无分支,时系统层次遍历的最小单位
案例实现
软件菜单案例
需求
类图
实现
抽象根节点:菜单组件
树枝节点:菜单类
子主题
叶子节点:菜单项
子主题
测试:Client
子主题
运行结果
分类
子主题
使用场景
出现树形结构的地方,比如文件目录显示、多级目录呈现等树形结构数据的操作
享元模式
定义
运用共享技术来有效地支持大量细粒度对象的复用,它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率
结构
享元(Flyweight)模式中存在两种状态
内部状态
不会因为环境的改变而改变的可共享部分
外部状态
随环境改变而改变的不可共享部分
享元模式实现的要领
区分应用中的这两种状态,并将外部状态外部化
角色
案例
俄罗斯方块
需求背景
在俄罗斯方块这个游戏中,每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,可以利用享元模式来实现
类图
实现
抽象享元角色
具体享元角色:I图形
具体享元角色:L图形
参考 I 图形
具体享元角色:O图形
参考 I 图形
工厂类
测试类:Client
优缺点
优点
极大减少内存中相似或相同对象的数量,节约系统资源,提供系统性能
享元模式中的外部状态对象相对独立,且不影响内部状态
缺点
为了是对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态、使程序逻辑复杂
使用场景
一个系统有大量相同或相似的对象,造成内存的大量消耗
对象大部分状态都可以外部化,可以将这些外部状态传入对象中
在该模式中需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此应当在需要多次重复使用享元对象时才值得使用享元模式
(类)行为型模式
模板方法模式
概述
定义一个操作中的算法股价,而将算法的一些步骤延迟到子类中,使得子类可以不改变算法结构骨架的情况下重新定义特定步骤
角色
抽象类
负责给出轮廓和骨架,由一个模板方法和若干个基本方法构成
模板方法:定义了算法的骨架,按照某种顺序调用其包含的基本方法
基本方法:实现算法的各个步骤方法,模板方法的组成部分,分为三种
抽象方法
一个抽象方法,由抽象类声明,由具体子类实现
具体方法
由抽象类或具体子类实现,子类可以覆盖,也可以继承
钩子方法
在抽象类中已实现,包括用于判断逻辑方法和需要子类重写的空方法两种
一般钩子方法时用于判断的逻辑方法,一般命名isXxxx,返回类型为 boolean
具体子类
实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤
案例(炒菜)
说明
步骤时固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤,可以通过模板方法实现
类图
实现
抽象类(定义模板方法和基本方法)
具体类(炒包菜类)
具体类(炒菜心类)
参考包菜
使用场景
算法步骤固定,个别部分易变的情况
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制
解释器模式
定义
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子
在解释器模式中,需要将待解决的问题提取出规则,抽象为一种语言
比如加减法运算,规则为:由数值和+-符号组成的合法序列,"1 + 2 + 3 - 4" 就是这种语言的句子
比如加减法运算,规则为:由数值和+-符号组成的合法序列,"1 + 2 + 3 - 4" 就是这种语言的句子
抽象语法树
在计算机科学中,抽象语法书(AbstractSyntaxTree, AST),简称(Syntax tree),是源代码语法结构的一种抽象表示,
它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构
它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构
用树形来表示符合文法规则的句子 :1 + 2 + 3 - 4
角色
抽象表达式(Abstract Expression)角色
定义解释器接口,约定解释器的解释操作,主要包含解释方法 interpret()
终结符表达式(Terminal Expression)角色
是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应
非终结符表达式(Nonterminal Expression)角色
是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每一条规则都对应于一个非终结符表达式
环境(Context)角色
通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值
客户端(Client)
主要是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,也可以通过环境角色间接访问解释器的解释方法
案例
需求
设计实现加减法软件
类图
实现
抽象表达式角色
环境角色
用于封装变量的类
加法表达式类
减法表达式类
测试类Client
优缺点
优点
易于改变和扩展文法:该模式用类来表示语言的文法规则,因此可以通过继承等机制改变或扩展文法,每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言
实现文法较为容易:在抽象语法树中,每一个表达式节点实现方式都很类似,这些类的代码编写都不会特别复杂
增加新的表达式较方便:如果要新增一个表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无需修改
缺点:
对于复杂文法难以维护:每条规则至少需要一个类,如果包含太多语法规则,类的个数急剧增加,导致系统难以管理和维护
执行效率低:在解释器模式中大量使用循环和递归的方式,因此在解释较为复杂的句子时,其速度会很慢,且代码的调试会比较麻烦
使用场景
当语言的文法较为简单,且执行效率不是关键问题时
当问题重复出现,且可以用一种简单的语言表达时
当一个语言需要解释执行,且语言中的句子可以表示为一个抽象语法树时
(对象)行为型模式 一
策略模式
定义
定义一系列算法,将每个算法封装起来,使它们相互替换,且算法的变化不会影响使用算法的客户
属于对象行为模式,通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象这些算法进行管理
角色
抽象(Strategy)策略类
抽象角色,通常由一个接口或抽象类实现,此角色给出所有的具体策略类所需的接口
具体(Concret Strategy)策略类
实现抽象策略定义的接口,提供具体的算法实现或行为
环境(Context)类
持有一个策略类的引用,最终给客户端调用
案例
促销活动
具体实现
抽象角色
具体角色
环境类
测试类Client
优缺点
优点:策略之间可以自由切换、易于扩展、避免使用多重条件选择语句 if else
缺点:Client必须要知道所有的策略类,并自行决定使用哪个策略、可能会产生很多策略
使用场景
一个系统需要动态地在几种算法中选择一种时、可将每个算法封住为策略
一个类定义了多种行为,且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入倒它们各自的策略类中代替这些条件语句
系统中各算法彼此完全独立,且要求对Client隐藏具体算法的实现细节
命令模式
定义
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分隔开,调用者和接收者之间通过命令对象进行沟通,两个对象实现解耦
这样方便将命令对象进行存储、传递、调用、增加与管理
角色
抽象命令(Command)角色
定义命令的接口,声明执行的方法
具体命令(Concrete Command)角色
具体的命令,实现命令接口;通常会持有接受者,并调用接收者的功能来完成命令要执行的操作
实现者/接收者(Receiver)角色
接收者、真正执行命令的对象,任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能
调用者/请求者(Invoker)角色
要求命令对象执行请求,通常持有命令对象,可以持有很多命令对象,这个是Client真正出发命令并要求命令执行相应操作的地方,相当于使用命令对象的入口
案例
背景(顾客点菜,服务员发命令给厨师)
类图
实现
订单类
抽象命令类
具体命令类
请求者角色:服务员类
接收者角色:厨师类
测试类:Client
优缺点
优点:降低耦合度,调用者和接收者解耦、增加删除命令非常方便、方便实现Undo和Redo操作,可以和备忘录模式结合实现命令撤销和恢复
缺点:系统结构变复杂、可能会导致某些系统由过多的具体命令类
使用场景
系统需要将调用者和接受者解耦,使得两者不能直接交互
系统需要支持命令的撤销(Undo)和恢复(Redo)
系统需要在不同的时间指定请求、将请求排队和执行请求(谁先下单,先做谁的)
责任链模式
概述
请假案例:请假者根据自己的职位请假、可能会找负责人、副总经理、总经理... 签名,这样请假者必须得记住这些领导姓名,电话,地址等信息
这样的例子有很多,比如领导出差报销、击鼓传花等游戏,常规处理会很麻烦,这样可以采用责任链模式来处理
定义
为了避免请求发出者和多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住下一个对象的引用而连成一条链
当有请求发生时,可将请求沿着这条链传递、直到有对象处理它为止
角色
抽象处理者(Handler)角色
定义一个处理请求的接口/抽象类,包含抽象处理方法和一个后继连接
具体处理者(Concrete Handler)角色
实现抽象处理的处理方法,判断能否处理本次请求,如果可以处理则处理,则该请求转给它的后继者
客户类(Client)角色
创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程
案例
需求/类图
实现
请假条类
抽象处理者
具体处理者:部门leader
具体处理者:部门经理
具体处理者:老板
Client
优缺点
优点:降低耦合度、增强扩展性、增强了给对象指派职责的灵活性、简化对象连接、责任分担
缺点:不能保证每个请求一定被处理、增加了客户端的复杂性
状态模式
引入案例
概述:通过按钮来控制一个电梯的状态,一个电梯有开门、关门、运行、停止四种状态,每一种状态都可能要根据其他状态来更新处理,例如:如果电梯时运行状态,就不能进行开门操作,如果时停止,就可以执行开门操作等
实现(switch 很繁琐):
定义
对于有状态的对象,把复杂的”判断逻辑“提取倒不同的状态对象中,允许状态对象在其内部状态发生改变时改变行为
角色
环境(Context)角色
也称上下文,定义了客户程序需要的接口,维护一个状态,并将与状态相关的操作委托给当前状态对象来处理
抽象状态(State)角色
定义一个接口,用以封装环境对象中的特定状态所对应的行为
具体状态(Concrete State)角色
实现抽象状态对象所对应的行为
案例改进
类图
子主题
实现
环境角色类:Context
抽象状态类:LiftState
具体状态类:OpenningState
具体状态类:CloseingState
具体状态类:RunningState
具体状态类:CloseingState
客户端类:Client
优缺点
优点:
将所有状态有关的行为放到一个类中,并可以方便的增加新的状态,只需要改变对象状态即可改变对象的行为
运行状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
缺点:
增加系统类和对象的个数、使用不当可能会导致程序结构复杂、代码混乱,状态模式对开闭原则的支持不太友好
使用场景
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式
一个操作中含有庞大的分支结构,并且这些分支取决于对象的状态时
观察者模式
定义
又称发布订阅模式,定义了一种一对多的以来关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己
角色
Subject:抽象主题(抽象被观察者)
把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象
ConcreteSubject:具体主题(具体被观察者)
该角色将有关状态存入具体观察者对象,在具体主题的内部发生改变时,给所有注册过的观察者发送通知
Observer:抽象观察者
是观察者的抽象类,定义了一个更新接口,使得在得到主题更改通知时更新自己
ConcreteObserver:具体观察者
具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态
案例
需求和类图
具体实现
抽象主题角色类
具体主题角色类
抽象观察者类
具体观察者角色类
测试类
优缺点
优点:降低了目标和观察者之间的耦合关系、可实现广播机制
缺点:如果观察者过多,会非常耗时,如果观察者有循环依赖,可能导致系统崩溃
JDK有提供
Observer 接口和 Observable 抽象类
(对象)行为型模式 二
中介者模式
概述
定义
又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互
角色
抽象中介者(Mediator)角色
它是中介者接口,提供了同事对象注册与转发同事对象信息的抽象方法
具体中介者(ConcreteMediator)角色
实现中介者接口,定义一个List来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色
抽象同事类(Colleague)角色
定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能
具体同事类(ConcreteColleague)角色
抽象同事类的实现者,当需要和其他同事对象交互时,由中介者对象负责后续的交互
案例
租房案例
背景
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息,房屋中介充当租房者与房主之间的中介者
类图
实现
抽象中介者角色
抽象同事角色
具体中介者角色
具体同事角色:租房者
具体同事角色:房主
测试类:Client
优缺点
优点:松散耦合,将多个同事之间的交互封装倒中介者对象,集中控制交互,交互行为发生改变后只改中介者对象即可,一对多关联转变为一对一关联
缺点:同时类太多的情况下,中介者职责将很大,它会变得复杂而庞大,可能会使得系统很难维护
使用场景
系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解
当创建一个运行于多个类之间的对象,又不想生成新的子类时
迭代器模式
定义
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
角色
抽象聚合(Aggregate)角色
定义存储、添加、删除聚合元素以及创建迭代器对象的接口
具体聚合(ConcreteAggregate)角色
实现抽象聚合类,返回一个具体迭代器的实例
抽象迭代器(Iterator)角色
定义访问和遍历聚合元素的接口,通常包含hasNext()、next() 等方法
具体迭代器(ConcreteIterator)角色
实现抽象迭代器接口中所定义的方法、完成对聚合对象的遍历,记录遍历的当前位置
案例
需求
定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现
类图
实现
Student类:
省略
抽象迭代器角色接口
具体迭代器角色类
抽象聚合角色 接口
具体聚合角色类
Client
访问者模式
定义
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作
角色
抽象访问者(Visitor)角色
定义了一个对每一个元素访问的行为,它的参数就是可以访问的元素,他的方法个数理论上与元素类个数(Element的实现类个数),从这点不难看出,访问者模式要求元素类的个数不能改变
具体访问者(ConcreteVistor)角色
给出对每一个元素类访问时所产生的具体行为
抽象元素(Element)角色
定义了一个接收访问者的方法(accept),其意义时是指,每一个元素都可以被访问者访问
具体元素(Concrete)角色
提供接收访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法
对象结构(Object Structure)角色
定义当中所提到的对象结构,对象结构是一个抽象描述,具体点可以理解为一个具有容器性质或符合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问
案例
需求
给宠物喂食
类图
实现
抽象访问者角色类(Person)
具体访问者角色类(自己)
具体访问者角色类(其他人):参考(自己)
抽象元素角色类(Animal)
具体元素角色类 (宠物猫)
具体元素角色类 (宠物狗):参考宠物猫
对象结构类(Home)
测试类(Client)
优缺点
优点:扩展性好(不修改对象结构中元素的情况下,为对象结构中的元素添加新的功能)、复用性好(通过访问者来定义整个对象结构通用的功能)、分离无关行为(通过访问者来分离无关的行为,把相关行为封装在一起构成一个访问者,这样每个访问者的功能都比较单一)
缺点:对象结构变化困难(每增加一个新的元素类,都要在每一个具体访问者类中增加相应具体操作,违背了开闭原则),违反了依赖倒置原则(访问者模式依赖了具体类,而没有以来抽象类)
使用场景
对象结构相对稳定,但其操作算法经常变化的程序
对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构
备忘录模式
概述
定义
又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态
角色
发起人(Originator)角色
记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息
备忘录(Memento)角色
负责存储发起人的内部状态,在需要时提供这些内部状态给发起人
管理者(Caretaker)角色
对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问和修改
备忘录有两个等效的接口
窄接口
管理者对象看到的是备忘录的窄接口,这个窄接口只允许它把备忘录对象传给其它对象
宽接口
与管理者看到的窄接口相反,发起人对象可以看到一个宽接口,允许它读取所有数据,以便根据这些数据恢复这个发起人对象的内部状态
案例
背景
游戏挑战BOSS,游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打boss前和后一定会不一样的,我们允许玩家如果感觉和Boss决斗的效果不理想时,可以让游戏恢复到决斗之前的状态
实现
白箱备忘录模式
白箱
说明:在管理者对象或客户端中可以对备忘录中的内容进行操作
弊端:破坏了封装性
类图
实现
发起人角色:游戏角色类
备忘录角色:角色状态类
管理者角色:状态管理类
测试工具类:Client
黑箱备忘录模式
黑箱:
类图
实现
备忘录接口
发起人角色
管理者角色
测试类Client
0 条评论
下一页