前言
设计模式一直困扰我很久,看了《大话设计模式》一书,看了几遍,刚看完,放下书就不记得了,那种挫败感,别说了!这次下了狠心了,找了些优质资源,结合书籍学习,再整理文档,将设计模式分类后,各个模式间的区别弄清楚后,脑子里对各个模式清晰了不少,不至于看了就忘了,本人脑子不好使,还是有些忘掉了,但没关系,第一不会忘记所有的,第二再看一眼自己的整理立马能记起来,以后工作中应该会有很多帮助,希望这个文章能对在学习设计模式的你有帮助,好了,下面就开始吧。
原则和设计模式具体的定义和说明,存在本人个人的理解,若有错误或不清晰的地方,以《大话设计模式》和网站中说明为主。其中部分设计模式的UML是自己画的,比较糙,还请读者到网站中查看相关内容。
学习的同学可以根据需要写个类似的文档以便理解和回顾,除了学习内容,一下的内容都可以只看框框,具体的说明都可以不参考我的。
学习的资源
视频资源:www.bilibili.com/video/BV1Np…(资源不完整,评论区有指导下载完整的,尚硅谷的官网有所有的java学习资源,一般还是可以找到的,若实在找不到,请留言)
网站资源:c.biancheng.net/view/1317.h…(这是大佬们整体的网站,里面讲的很详细,包括设计模式间的对比)
学习内容:有步骤地学习能更好的理解。
1、设计模式的目的
2、七大原则
3、UML图
4、创建型模式
5、结构型模式
6、行为型模式
设计模式的目的:
- 代码重用性
- 可读性
- 可扩展性
- 可靠性
- 高内聚、低耦合的特性
七大原则
单一原则
一个类只负责一项职责(比如:订单,派送是两个职责),或者说引起这个类变化的只有一个原因。
接口隔离原则
接口范围最小化,需要哪些接口就使用哪些接口,若出现多余方法,则拆分接口。
依赖倒置原则
高层模块不要依赖底层模块,二者依赖抽象类和接口
抽象不应该依赖细节,细节应该依赖抽象
里氏替换原则
把父类都替换成它的子类,程序行为没有变化。
简单说,子类型必须能够替换掉他们的父类型。
1、子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
2、子类中可以增加自己特有的方法
使用继承的准则--尽量不要重写父类的方法
继承会带入一定的弊端--侵入性,可移植性降低,增加对象间的耦合性,父类的修改会影响到所有子类的功能。
非要重写,可通过聚合,组合,依赖来解决问题。
开闭原则
扩展开放(对提供方),修改关闭(对调用方)。
迪米特原则(最少知识原则)
针对类之间依赖的情况做出的规范--尽量降低成员的访问权限。
直接朋友:成员变量,方法返回值,方法参数
局部变量则是非直接朋友,若出现则需要将此部分抽出到相应的类中,即怎么来的就怎么还回去。
合成复用原则
尽量使用聚合/合成的方式,则不是使用继承。
若一个类A只是想使用另一个类B的方法,而没有密切的关系,若使用继承则会增强耦合性,此时使用合成和聚合则会更佳。
B对象的传入到A的方式:
- 作为成员变量直接new-- b : B =new B();---组合关系
- 通过b:B; setB(b:B)--聚合关系
- 作为方法参数传入 operation(b:B)--依赖关系
UML图
简单说明:
聚合:A中有B类,通过setB(B b)方法来实现。
组合:通过直接new出来对象作为成员变量即为组合。
类图
类之间的关系:
依赖(Dependence):虚线+箭头
类中用到了对方,他们之间存在依赖关系。
泛化(继承):实线+空心三角箭头
依赖关系的特列,继承关系。
实现:虚线+空心三角箭头
依赖关系的特列,接口的实现
关联(relation):实线+箭头
导航性:双向关系或单向关系。
聚合(Aggregation):箭头+实线+空心菱形(菱形对着被聚合的对象,比如电脑(聚合对象))
关联关系的特例。
可以分离为聚合关系(电脑:鼠标,显示器,可以分离,为聚合关系)
A中有B类,通过setB(B b)方法来实现。
组合(composite):箭头+实线+实心菱形
关联关系的特例。
整体和部分的关系,但不可分开。(人和头的关系)
通过直接new出来对象作为成员变量即为组合。
设计模式类型
考虑思路:
0、模式定义?
1、模型如何实现?
2、模式存在的意义
3、模式使用的场景
创建型模式:对象的创建与使用分离
单例模式、工厂模式、抽象工厂模式、原型模式、建造者模式
单例模式
前提: 1、构造方法私有化,
| 实现方式 | 优势 | 劣势 | 使用场景 |
|---|---|---|---|
| 饿汉式(静态常量) | 避免同步问题 | 容易内存浪费(类装载时实例化) | 确认一定会用 |
| 饿汉式(静态代码块) | 同上 | 同上 | 同上 |
| 懒汉式(线程不安全) | 不使用此方式 | ||
| 懒汉式(线程安全) 使用同步锁锁住静态方法 | 解决安全问题 | 效率低 | 不推荐使用 |
| 懒汉式(同步代码块) | 未能解决安全问题 | 不推荐使用 | |
| 懒汉式(双重检查) | 解决安全问题和效率问题 | 推荐使用 | |
| 静态内部类 静态内部类在外部内加载时不会加载,而且只加载一次,并是线程安全的 | 线程安全,延时加载,效率高 | 推荐使用 | |
| 枚举 | 线程安全,防止反序列化 | 推荐使用 |
使用场景:
工厂模式
三个工厂模式的区别:
简单工厂模式
简单工厂模式不是23种里的一种,简而言之,就是有一个专门生产某个产品的类。 比如下图中的鼠标工厂,专业生产鼠标,给参数0,生产戴尔鼠标,给参数1,生产惠普鼠标。
工厂模式
工厂模式也就是鼠标工厂是个父类,有生产鼠标这个接口。 戴尔鼠标工厂,惠普鼠标工厂继承它,可以分别生产戴尔鼠标,惠普鼠标。 生产哪种鼠标不再由参数决定,而是创建鼠标工厂时,由戴尔鼠标工厂创建。 后续直接调用鼠标工厂.生产鼠标()即可
抽象工厂模式
抽象工厂模式也就是不仅生产鼠标,同时生产键盘。 也就是PC厂商是个父类,有生产鼠标,生产键盘两个接口。 戴尔工厂,惠普工厂继承它,可以分别生产戴尔鼠标+戴尔键盘,和惠普鼠标+惠普键盘。 创建工厂时,由戴尔工厂创建。 后续工厂.生产鼠标()则生产戴尔鼠标,工厂.生产键盘()则生产戴尔键盘。
在抽象工厂模式中,假设我们需要增加一个工厂
假设我们增加华硕工厂,则我们需要增加华硕工厂,和戴尔工厂一样,继承PC厂商。 之后创建华硕鼠标,继承鼠标类。创建华硕键盘,继承键盘类。 即可。
在抽象工厂模式中,假设我们需要增加一个产品
假设我们增加耳麦这个产品,则首先我们需要增加耳麦这个父类,再加上戴尔耳麦,惠普耳麦这两个子类。 之后在PC厂商这个父类中,增加生产耳麦的接口。最后在戴尔工厂,惠普工厂这两个类中,分别实现生产戴尔耳麦,惠普耳麦的功能。 以上。
| 模式 | 实现说明 | 优点 | 缺点 |
|---|---|---|---|
| 简单工厂模式 | 通过一个工厂类创建对象,通过判断条件区分创建的对象 | 实现简单,容易理解 | 违背了开闭原则,每新增分支都需要修改工厂类 |
| 工厂方法模式 | 抽象一个工厂基类,对不同的对象有不同的工厂类,变动移到了客户端。 | 遵守了开闭原则 | 类增加过多 |
| 抽象工厂模式 | 在工厂方法的前提下增加了系列,一个系列的对像创建 | 主要解决系列问题 | 客户都调用过多时修改还是过多 |
| 简单工厂+反射 | 通过简单工厂提供创建方法,但创建对象通过类型判别具体的class,反射生成需要的对象,去掉了case的判断,此时就可以不用再修改了 | 实现简单,扩展性强,变动少 |
原型模式
深浅拷贝
定义
原型模式就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。
实现
针对对象的拷贝,使用重写Object中的clone()方法,和序列化的方式拷贝对象。
浅拷贝:成员变量为对象,则直接复制的对象指引,并不会生成新的成员变量对象
深拷贝:重写成员变量的对象clone方法,在在clone中调用变量的clone(),并赋值。
注意: 重写clone需要对象继承Cloneable,原因:Object中clone方法有判断对象是否是Cloneable。
序列化:类实现序列化,将对象转为字节流,再将字节流转为对象。
存在的意义
防止客户端多此创建相同属性的对象,同时隐藏了对象创建的细节,也提高了性能。
使用场景
一般在初始化的信息不发生变化的情况下,克隆是最好的办法。
建造者模式
流程稳定,创建对象
定义:
复杂对象,创建过程稳定,细节不同。
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
实现:
通过抽象builder,builder实现s创建细节,Director聚合builder接口,并调用builder的创建步骤。
抽象建造者,实现建造者,传入不同建造者,生成不同对象。
意义:
封装创建的细节,同时可以扩展,只要针对自己的情况实现builder细节,客户端调用新的builder实现就行。
场景:
针对固定的创建对象流程,但细节繁琐,容易出错的情况。
结构型模式:软件系统结构层面考虑,将类或对象按某种布局组成更大的结构。
类结构型模式: 采用继承机制来组织接口和类
对象结构型模式: 釆用组合或聚合来组合对象
包含:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
适配器模式
接口转换,兼容
定义:
将一个类的接口转换成客户希望的另外一个接口。
实现:
对象适配器模式(聚合--成员变量),类适配器模式(继承新类,实现老接口),接口适配器模式(抽象实现接口,对不需要的函数对外不可见)
意义:
封装创建的细节,同时可以扩展,只要针对自己的情况实现builder细节,客户端调用新的builder实现就行。
场景:
希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。
两个类所做的事情相同或相似,但是具有不同的接口要使用它。
桥接模式
抽象和实现独立,聚合到抽象中
定义:
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
抽离接口实现,将接口以聚合形式关联抽象,由客户端传递具体地接口派生类。
实现:
意义:
解决类爆炸问题。多角度地分离,若还是继承关系,新加一个类型会新建很多类,导致类爆炸。
场景:
- 1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- 2、
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。 - 3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
装饰模式(IO流地嵌套就是装饰者模式)
动态增加功能
定义:
动态地给一个对象添加一些额外地职责,就增加功能来说,装饰模式比生孩子更为灵活。
实现:
一层层包裹对象,后通过递归执行到所有对象特定地方法。
Component1 component1=new Component1();
Decorator decorator1 = new Decorator1(component1);
Decorator decorator2 = new Decorator1(decorator1 );
decorator2 .Operation1();
意义:
有效地把类地核心职责和装饰功能区分开,可以去除相关类中重复地装饰逻辑。
将装饰功能放到单独的类中,并让这个类包装他要装饰的对象,当需要时,客户代码就可以运行时根据需要有选择地、按顺序地使用装饰功能包装对象。
场景:
动态地给一个对象添加一些额外地职责,并新加入的东西仅仅为了满足一些只在特定情况下才会执行地特殊行为的需要。
组合模式(hashMap使用的就是组合模式)
部分和整体一致性
定义:
将对象组合成树形结构以表示“部分-整体”地层次结构。组合模式使得用户对单个对象和组合对象地使用具有一致性。
实现:
对原理结构图的说明-即(组合模式的角色及职责)
-
Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子部件, Component 可以是抽象类或者接口
-
Leaf : 在组合中表示叶子节点,叶子节点没有子节点
意义:
组合模式解决这样的问题,当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子。
场景:
需求中是体现部分与整体层次地结构时,希望用户可以忽略组合对象和单个对象地不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式。
外观模式(Facade)
分层隔离,多子系统统一接口,复杂架构开放类
定义:
为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
就如:客户-->基金经理(高层接口)-->股票
实现:
子系统(真实执行系统)
外观类(统一调用子系统,并提供外部接口给客户端)
意义:
执行流程比较繁琐,若直接让客户端调用,会繁琐容易出错,所以就加了一个外观层,提供接口给客户端调用。
降低复杂性:对外屏蔽了子系统的细节,降低客户端对子系统使用的复杂性
扩展性和易维护:解耦客户端,利于子系统的变更。
层次性:划分访问层次
与建造者模式区别:建造者为了创建对象方便,外观模式为了对象调用方便灵活。
场景:
1、设计初期阶段,有意识地将不同地两个层分离
2、开发阶段,子系统往往有因为不断的重构演化而变得越来越复杂,增肌外观Facade可以提供一个简单的接口,减少它们之间的依赖
3、维护阶段,对于遗留的非常难以维护和扩展的大型系统,使用外观Facade层与遗留代码交互所有复杂工作,而开放简单接口与新系统对接。
享元模式(Flyweight Pattern):池技术
共享,池技术
定义:
运用共享技术有效地支持大量细粒度的对象。
实现:
对原理图的说明-即(模式的角色及职责* )*
-
FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态(不能共享)和内部状态(能共享)的接口或实现
-
ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
-
UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂
-
FlayweightFactory享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象方法。
意义:
1、解决重复对象的内存浪费的问题。
优点
– 极大减少内存中对象的数量 – 相同或相似对象内存中只存一份,极大的节约资源,提高系统性能 – 外部状态相对独立,不影响内部状态
• 缺点
– 模式较复杂,使程序逻辑复杂化 – 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态 使运行时间变长。用时间换取了空间。
场景:
1、一个程序使用大量的对象,而大量的对象造成了很大的存储开销时就应该考虑使用。
2、对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。
代理模式(android的AIDL就是使用远程代理模式)
控制对象的访问
定义:
为其他对象提供一种代理以控制对这个对象的访问。
实现:
1、静态代理:
代理和被代理都实现相同接口或抽象,代理类中聚合被代理类。
2、动态代理
在静态代理的基础上加上jdk的Proxy动态生成代理对象的机制。
3、Cglib代理(需要引入cglib包)
和动态代理的区别:是否需要实现接口,实现接口就使用动态代理,不实现接口就使用Cglib代理。如下图:TeacherDao未实现接口。
意义:
- 中介隔离作用: 在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- 开闭原则,增加功能: 代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。
1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
场景:
(1)当我们想要隐藏某个类时,可以为其提供代理类
(2)当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中进行权限判断来进行不同权限的功能调用)
(3)当我们要扩展(为了控制)某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展(只针对简单扩展,可在引用委托类的语句之前与之后进行)
行为型模式:方法层面考虑
模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)
模板方法模式
骨架稳定,细节变化,产生不同算法。
定义:
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
实现:
1、将所有子类的相同部分提到父类中
2、父类中实现流程调用,即算法的骨架。
3、子类中针对部分步骤使用不同实现,从而达到实现不同的算法。
4、钩子方法:父类中定义空的虚方法,子类可以通过重写,实现不同算法,也可以不重写。
意义:
1、封装不变部分,扩展可变部分。具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构 2、提取公共代码,便于维护。代码复用的基本技术,在数据库设计中尤为重要。 3、行为由父类控制,子类实现。存在一种反向的控制结构,通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。
场景:
1、有多个子类共有的方法,且逻辑相同。
2、重要的、复杂的方法,可以考虑作为模板方法。
和其他模式的区别
模板方法和建造者方法设计思想大同小异,不同点在于:
模板方法:将算法流程封装在父类中,让子类实现部分步骤。
建造者方法:将创建流程在指挥者类中创建调用,抽象类基本是定义抽象方法,没有具体的实现。
但说实在的,建造者也可以将流程封装在抽象类中,这一变感觉就成了模板方法模式了。
命令模式
请求和执行解耦
定义:
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
实现:
1、命令调度员Invoker
2、抽象的命令接口
3、真实命令实现者ConcreteCommand,内聚真实执行命令的接收者Receiver;
4、客户端先初始化命令调度员和命令执行者及接收者
5、再有调度员添加命令,并统一调度执行。
结合备忘录模式,增加可回退功能:
意义:
1、将发起请求的对象和执行请求的对象解耦。
场景:
- 请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
- 系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能。
- 当系统需要执行一组操作时,命令模式可以定义宏命令(命令模式+组合模式)来实现该功能。
- 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。
访问者模式
数据结构相对稳定,访问方式却多种多样,使用“访问者模式”将访问方式从数据结构中分离出来,通过抽象访问方式,又可增加扩展。
定义:
表示一个作用于某对象结构中的各元素的操作,使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
实现:
意义:
将数据结构中的操作数据和元素分离开,以便元素不变,扩展操作方式,达到不同结果。
场景:
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
- 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
迭代器模式(List,Map,Set都有迭代器模式来遍历)
针对对象中不同类型聚合进行遍历。
定义:
提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
其目的是:在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。
实现:
1、通过集合控制类的实现类,聚合迭代器的实现类,并传入数据给迭代器
2、若控制类中有相同元素但存储元素的集合有多种,可以聚合多个迭代器类,并提供统一方法遍历所有集合数据。
意义:
多了一种遍历,并不会暴露内部细节,可为不同的聚合结构提供统一的接口,如Conllection
场景:
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为遍历不同的聚合结构提供一个统一的接口时。
- 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
观察者模式
一对多的依赖关系,“一”变化引起“多”变化
定义:
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
实现:
将观察者通过add注入到变化对象种,一旦发生变化,通知所有观察者更新变化
意义:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
- 新增依赖关系可直接新增观察者,增强扩展性
场景:
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
备忘录模式
记录部分状态
定义:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
实现:
-
originator : 对象(需要保存 状态的对象)
-
Memento : 备忘录对象,负责保存好记录,即Originator内部状态
-
Caretaker: 守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效率
-
说明:如果希望保存多个originator对象的不同时间的状态,也可以,只需要 HashMap
意义:
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
场景:
1、后悔药,用于回退
2、游戏存档
3、数据库的事务管理
中介者模式
多个对象连接,使用中间者与各个对象形成一一对应关系
定义:
用一个中介对象来封装一系列的对象交互。中介者使个对象不需要显示的相互引用,从而松耦合,并可独立地改变它们之间地交互。
实现:
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
扩展:将中介者使用单例模式,可以不需要抽像,同时同事类也不需要聚合中介者,需要使用时直接调用就好。
意义:
1、对象间各司其职,复合迪米特法则。
2、将对象间复杂关系统一由中间者管理,解除对象间地依赖关系
3、关系地变更和新增,只需修改中介类就好,虽然不是完美地避免开闭原则,但将修改规避到最小范围。
场景:
1、对象间复杂地网状结构关系导致依赖关系混乱且难以复用
2、想创建一个运行于多个类之间地对象,又不想生成新的子类时。
解释器模式:没兴趣,大概率也用不到不看了
状态模式
对象内部状态变化,行为能跟随改变 状态类可给环境进行设定状态。
定义:
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
实现:
和策略模式基本一致,只是状态类内部稍有不同,状态模式的状态类中有判断状态,给环境设定其他状态,这样才能跟随状态变化,而行为变为,其实是给环境重新设定了状态类。
- 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
意义:
消除庞大的条件分支语句
场景:
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
状态模式与策略模式的区别:
状态模式和策略模式的 UML 类图架构几乎完全一样,但两者的应用场景是不一样的。策略模式的多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法,而状态模式的各个状态间存在相互关系,彼此之间在一定条件下存在自动切换状态的效果,并且用户无法指定状态,只能设置初始状态。
状态模式与责任链模式的区别:
从定义来看: 状态模式强调的是一个对象内在状态的改变,而责任链模式强调的是外部节点对象间的改变 。
从代码实现上来看: 两者最大的区别就是状态模式的各个状态对象知道自己要进入的下一个状态对象,而责任链模式并不清楚其下一个节点处理对象,因为链式组装由客户端负责。
策略模式
使用算法和算法实现分割开,由客户端给定具体的算法实现。
简单工厂+策略模式
定义:
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。
实现:
- 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
意义:
场景:
- 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
职责链模式(责任链模式)
解耦请求与处理,一个请求多个处理者
定义:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。
将这个对象连城一条链,并沿着这条链传递该请求,直到有一个对象处理它。
实现:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。