“设计模式”这个术语最初并不是出现在软件设计中,而是被用于建筑领域的设计中。
《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书,在本教程中收录了 23 个设计模式,这是设计模式领域里程碑的事件,导致了软件设计模式的突破。
狭义的设计模式还是本教程中所介绍的 23 种经典设计模式
概念
一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结
在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案
解决特定问题的一系列套路
目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
意义
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
复用可以分为三个层次。
例如,JUnit 是一个小型框架,也是框架的“Hello, world”,其中定义了 Test、TestCase 和 TestSuite 这几个类及其关系。
定义声明了框架中使用的类之间的关系,但是具体内容需要使用者去填充指定
框架通常比单个类的颗粒度要大。
你可以通过在某处构建子类来与框架建立联系。这些子类信奉“别给我们打电话,我们会给你打电话的。”
中间层次
设计模式所处的位置。设计模式比框架更小且更抽象。
一组类的关系及其互动方式的描述
设计模式要活学活用,不要生搬硬套。
http://c.biancheng.net/view/vip_8376.html
软件实体应当对扩展开放,对修改关闭
软件实体:
当应用的需求变更时,不修改实体中的代码,对功能进行拓展
例子:
如果有原来有个学生类Student具有书写和阅读功能属性,后来需要加上一个唱歌的属性,那么不会直接在Student中进行修改,而是通过其他方式新增这个功能属性:
子类可以扩展父类的功能,但不能改变父类原有的功能。
注意的定义:子类对于父类的方法上:宽入,严出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BfWEb2p9-1667801274423)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211204224647865.png)]
问题:几维鸟对父类的方法进行了修改
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SHJDMPW9-1667801274424)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211204224807513.png)]
寻找两者的共性,将共性再提取父类,鸟类中带有独特的飞行属性
最常用!!!!
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
面向接口编程,不要面向实现编程。
使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成。
实现方法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C30giHNe-1667801274429)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211204230139741.png)]
一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分
一个类只干一种事情
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gJRj9Fnt-1667801274429)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211204225959269.png)]
将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法
一个类对另一个类的依赖应该建立在最小的接口上
要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-km7Ugq2i-1667801274430)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211204230402487.png)]
使用接口对方法进行区分,通过接口进行方法的管理,不关注细节,注意力放在业务实现
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。
相关联的对象:
当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
实现方法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vaeidFZp-1667801274430)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211204230955323.png)]
使用中介类进行类之间的沟通和使用
复用的时候,优先考虑组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
想法思路:如果能够从已有的零件组装成需要的就优先组装,比如接口的综合搭配和属性来源的综合搭配
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iukvYfUm-1667801274430)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211204232732607.png)]
不从具体类中生成子具体类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oJtXuT71-1667801274430)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211204232907500.png)]
通过组合 组装一个有颜色的车
口诀:
访问加限制,函数要节俭,依赖不允许,动态加接口,父类要抽象,扩展不更改。
在程序设计时,我们应该将程序功能最小化,每个类只干一件事。若有类似功能基础之上添加新功能,则要合理使用继承。对于多方法的调用,要会运用接口,同时合理设置接口功能与数量。最后类与类之间做到低耦合高内聚。
将对象的创建与使用分离
分类:
单例(Singleton)
某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例
原型(Prototype)
一个对象作为原型,通过对其进行复制而克隆
工厂(FactoryMethod)
一个用于创建产品的接口,如何生产,生产什么,由具体的实现类进行
抽象工厂(AbstractFactory)
一个创建产品族的接口
建造者(Builder)
将一个复杂对象分解成多个相对简单的部分
一个类只有一个实例,且该类能自行创建这个实例的一种模式
Windows 中只能打开一个任务管理器
特点:
单例类只有一个实例对象
私有变量其中一个是实例化对象
该单例对象必须由单例类自行创建-----外部不能实例化
实例化函数私有
提供访问
只将私有化的对象提供一个访问的接口
实现方式:
在访问的时候检查是否有这个单例对象
饿汉
在加载类文件的时候就创建
拓展:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iVkkZpmp-1667801274431)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211206091136500.png)]
在加载类文件的时候进行创建
通过复制该原型对象来创建一个和原型相同或相似的新对象
Windows 操作系统的安装通常较耗时,如果复制就快了很多
结构
主要角色:
模式实现:
浅克隆
对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
Object 类提供了浅克隆的 clone() ,只需要实现Cloneable接口
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype) super.clone();
深克隆
引用的其他对象也会被克隆,不再指向原有对象地址。
public Object clone() {
SunWukong w = null;
try {
w = (SunWukong) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("拷贝悟空失败!");
}
return w;
}
在函数内部去父类进行复制,这样复制出来的就和原来的一样
生成相似的对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EcLdEue1-1667801274431)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211206094354890.png)]
相同类型的对象,通过克隆进行获得,那么复制后只需要修改对应部分就可以了
public static void main(String[] args) throws CloneNotSupportedException {
citation obj1 = new citation("张三", "同学:在2016学年第一学期中表现优秀,被评为三好学生。", "韶关学院");
obj1.display();
citation obj2 = (citation) obj1.clone();
obj2.setName("李四");
obj2.display();
}
拓展:
原型管理器 PrototypeManager 类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4dXMmMqe-1667801274432)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211206094619906.png)]
将可以实现了克隆接口的对象存到一个列表,取用的时候通过clone返回出去
生成复杂对象的地方
类的构造过于复杂,参数特别的多
定义:
一个创建产品对象的工厂接口
实际的创建在工厂的实现类中
抽象工厂—》工厂实现(静态类—工具类)
简单工厂模式有一个具体的工厂类,可以生成多个不同的产品
只使用一个工厂去生产产品
简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”
高度类似当下的工厂
结构:
具体产品由具体工厂进行创建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AlEiHuCm-1667801274432)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211206101310724.png)]
应用实例:畜牧场
考虑多等级产品的生产
是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如农场里既养动物又种植物,电器厂既生产电视机又生产洗衣机或空调
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nzABzPcb-1667801274432)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211206102109280.png)]
产品族:同一个牌子
产品登记:同一种东西
定义:
一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
结构:
主要角色:
抽象工厂
多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
理解
当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。
组合—积木搭模型
理解:
对于一个对象而言,比如人,就会有不变和变化的部分
不变:人都是有头,手,脚,躯干这些部分
变化:每个人的头,手,脚,在实际上是不一样的
都有,但都不一样
结构:
指挥者(指挥)—》建造者(创建)—》产品
public class Client {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
//创建指挥者的时候指定建造者
Product product = director.construct();
product.show();
}
}
应用实例:客厅装修
产品:客厅----多个组成部件
应用场景
针对复杂对象的创建
同样的创建行为可以生产出不同的产品
与工厂模式的区别
注重方法的调用顺序,工厂模式注重创建对象
一个是使用已有对象组装,一个是生产对象
建造出来的对象复杂多样,工厂生产出来的都是一样的
如何将类或对象按某种布局组成更大的结构
定义类或对象之间的关系,形成一个大的整体
分类:
类结构型(通过继承)
对象结构型(组合/聚合)
满足合成复用原则,更加有灵活性
分类:
一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务
中介就是这个代理
软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大
定义:
访问对象不能直接引用目标对象,需要通过一个中介
客户不能直接到厂家买电脑,需要通过中介
结构
抽象主题:使用抽象对象(接口,抽象类)实现的业务方法
真实主题:实现抽象主题,也是最终要引用的对象
代理类:提供与真实主题一样的接口,内部使用的都是真实主题的方法,但是在输入输出都可以进行变化。
在实际的生活中,代理提供了厂家的服务之外,还带有他自己提供的服务增益,但是用户感受不到区别,以为是厂家提供一样的一样
代理模式
静态
在代码运行前就已经写好了内容,不能改了,只能用
动态
通过反射机制,动态创建代理进行增强----厂家已经有了,但是可以动态地去生成一个代理来调用这个工厂的方法
反射:通过读取java的类文件.class获取类文件内容,加以使用
实际上都是使用了被代理对象的方法
应用场景
无法或不想直接引用某个对象或访问某个对象存在困难时
想购买一个北京的特产,没办法直接购买,只能通过中介(淘宝买手)
淘宝买手说售卖的价格是150,实际上在北京售卖100元(真实调用的方法),但是在买手这里买了还会送东西(post增强),而且如果你给的价钱不够(pre增强)还不给你买。
远程代理
隐藏目标对象
虚拟代理
将服务比较复杂的进行代理,比如下载一个东西的时候可能很慢,可以用虚拟代理进行下载,这个虚拟代理就会通过增强方法,如果下载慢或者不动了,就生成一个流畅的精度条
下载速度很慢或者卡住了,生成虚拟代理来代替这个图片,给访问了反馈下载速度。比如一张图片有1000m,无法一次下载到位,生成10个服务代理,每个发送100m,不然用户以为卡住了。
安全代理
智能指引
延迟加载
拓展
缺点:
代理必须实现和被代理对象的所有,一一对应,被代理对象修改了,代理也得修改
厂家不卖A产品,代理也没得卖
代理必须基于被代理对象存在
没有厂家,代理无法存活
动态代理
出现两个对象因接口不兼容而不能在一起工作
比如220V的电压不能直接使用来为手机充电,需要一个适配器进行电压的转换
在软件设计中,可能会出现已经存在需要的组件,但是两者并不能兼容地使用,A接口需要进行转换成B接口来使用
定义
一个类的接口转换成客户希望的另外一个接口,多使用对象结构型模式
在java中,使用继承+实现接口进行适配器的执行
结构
主要角色:
目标(Target)接口:最终使用的接口----40V
适配者(Adaptee):被适配的接口—比如上文提到的220V
适配器(Adapter):处理转换接口
使用过程:
继承被转换的类(220V)
实现目标的接口(40V)
实现的方法(40V)中调用继承(220V)的方法,然后进行处理(降压)
在访问的时候,通过输入被转换的对象来创建适配器,通过引入外部对象进行创建
所以在适配器内部具有一个被转换的对象变量作为构建函数的参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XtdqLW2v-1667801274434)(C:\Users\LittleBoy\AppData\Roaming\Typora\typora-user-images\image-20211207155919197.png)]
对象适配型
使用的时候将被转换的类作为私有变量在实现目标接口的方法中使用
static class MotorAdapter implements NewMotor {
OldMotor oldMotor;
public MotorAdapter(OldMotor oldMotor) {
this.oldMotor = oldMotor;
}
拓展
变成一个双向的适配器
A可以转换为B,B可以转换为A
在设配器中设置两个类的变量,在构造函数里面判断要转换的类是哪一个
在现实生活中,某些类具有两个或多个维度的变化
图形既可按形状分,又可按颜色分
如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。
继承形状来增加颜色
- 不同颜色和字体的文字、不同品牌和功率的汽车、不同性别和职业的男女
定义
将抽象与实现分离,使它们可以独立变化
使用组合代替继承
比如一个有颜色的车:
车+颜色
结构
主要角色:
使用:
通过在抽象角色中加入实现化类型的变量,将具体实现化引入,形成一个拓展的抽象角色
应用场景:
继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能
父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿
原则为优先使用组合/聚合,而不是继承。
拓展
使用适配器对实现化进行转化,组合到抽象化对象当中进行桥接达到拓展抽象化对象的效果
早餐可能会吃煎饼,煎饼中可以加鸡蛋,也可以加香肠,但是不管怎么“加码”,都还是一个煎饼。
在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。
定义
不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能
结构
角色:
使用方法:
在抽象装饰器中声明装饰谁–实现一样的接口
被装饰的组件作为构建函数的变量
调用被装饰的方法
在具体装饰器(继承抽象)实现中,从写调用方法并且增强
实现例子:
应用场景
FileReader 增加缓冲区而采用的装饰类 BufferedReader
BufferedReader in = new BufferedReader(new FileReader("filename.txt"));
String s = in.readLine();
办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了
为多个子系统提供一个统一的接口
结构
拓展
通过抽象外观解决子系统变更时产生的链接问题,修改子系统时,就不需要修改外观类。
创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源
如果能把它们相同的部分提取出来共享,则能节省大量的系统资源
定义
运用共享技术来有效地支持大量细粒度对象的复用。
实现
细粒度和共享对象
细粒度
对象信息分成两部分
内部状态
存储在享元信息内部,并且不随环境的改变而改变
外部状态
对象得以依赖的一个标记,随环境的改变而改变,不可共享
结构
角色:
实现:
非享元中注入享元变量具体方法中使用
应用场景
多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。
拓展
存在两种特殊的享元模式:单纯享元模式和复合享元模式
没有非享元成分
复合享元
单纯享元对象进行组合而成,不能直接共享,而是通过分解后进行共享
“部分-整体”
大学中的部门与学院、总公司中的部门与分公司
定义
整体-部分模式
将对象组合成树状的层次结构
整个树形结构中的对象都属于同一种类型
结构
角色:
抽象构件
主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为
树叶
没有子节点,用于继承或实现抽象构件
树枝
有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件
实现方式:
1.透明方式
管理子构件的方法移到树枝构件中
客户访问不能通过Component进行对树枝和树叶同时访问,因为两者的接口都被改了,需要分别访问
应用案例
用户在商店购物后,显示其所选商品信息,并计算所选商品总价的功能
假如李先生到韶关“天街e角”生活用品店购物,用 1 个红色小袋子装了 2 包婺源特产(单价 7.9 元)、1 张婺源地图(单价 9.9 元);用 1 个白色小袋子装了 2 包韶关香藉(单价 68 元)和 3 包韶关红茶(单价 180 元);用 1 个中袋子装了前面的红色小袋子和 1 个景德镇瓷器(单价 380 元);用 1 个大袋子装了前面的中袋子、白色小袋子和 1 双李宁牌运动鞋(单价 198 元)
树叶和树枝实现的方法实现物品接口的方法内容是不一样的
public class ShoppingTest {
public static void main(String[] args) {
float s = 0;
Bags BigBag, mediumBag, smallRedBag, smallWhiteBag;
Goods sp;
BigBag = new Bags("大袋子");
mediumBag = new Bags("中袋子");
smallRedBag = new Bags("红色小袋子");
smallWhiteBag = new Bags("白色小袋子");
sp = new Goods("婺源特产", 2, 7.9f);
smallRedBag.add(sp);
sp = new Goods("婺源地图", 1, 9.9f);
smallRedBag.add(sp);
sp = new Goods("韶关香菇", 2, 68);
smallWhiteBag.add(sp);
sp = new Goods("韶关红茶", 3, 180);
smallWhiteBag.add(sp);
sp = new Goods("景德镇瓷器", 1, 380);
mediumBag.add(sp);
mediumBag.add(smallRedBag);
sp = new Goods("李宁牌运动鞋", 1, 198);
BigBag.add(sp);
BigBag.add(smallWhiteBag);
BigBag.add(mediumBag);
System.out.println("您选购的商品有:");
BigBag.show();
s = BigBag.calculation();
System.out.println("要支付的总价是:" + s + "元");
}
}
//抽象构件:物品
interface Articles {
public float calculation(); //计算
public void show();
}
//树叶构件:商品
class Goods implements Articles {
private String name; //名字
private int quantity; //数量
private float unitPrice; //单价
public Goods(String name, int quantity, float unitPrice) {
this.name = name;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
public float calculation() {
return quantity * unitPrice;
}
public void show() {
System.out.println(name + "(数量:" + quantity + ",单价:" + unitPrice + "元)");
}
}
//树枝构件:袋子
class Bags implements Articles {
private String name; //名字
private ArrayList<Articles> bags = new ArrayList<Articles>();
public Bags(String name) {
this.name = name;
}
public void add(Articles c) {
bags.add(c);
}
public void remove(Articles c) {
bags.remove(c);
}
public Articles getChild(int i) {
return bags.get(i);
}
public float calculation() {
float s = 0;
for (Object obj : bags) {
s += ((Articles) obj).calculation();
}
return s;
}
public void show() {
for (Object obj : bags) {
((Articles) obj).show();
}
}
}
拓展
对树枝结点和树叶结点进行抽象,进行复杂的组合模式
描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配
知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关
例子:
一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。
将已经规定好的流程或者格式的实例定义成模版,允许使用者更具自己的需求去更新
简历模板、论文模板、Word 中模板文件等。
定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
注意抽象类与具体子类之间的协作
结构
角色:
抽象类/抽象模板(Abstract Class)
给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
具体实现
例子:出国的步骤是一样的,可以某些针对目的国的方法是不一样的,需要在实现时根据对象进行设计
使用实现
在模版方法中按照逻辑实现方法,其中方法可以是确定的,和不确定的(留给子代补充实现)
拓展
对于钩子函数
钩子函数开放给子代进行设计,里面使用的是已经确定的方法
实现某种目标存在多种策略可供选择的情况
出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法
在软件实现中可能对一个实现有很多种策略可以进行实现
数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等
不可能每一个方法都放入类的编辑设计,然后通过多重条件进行判断。
定义
定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户
使用算法的责任和算法的实现分割开来
理解:
相当于将方法的实现装到不同的地方(策略实现类),通过策略访问类进行获取和使用
实现例子:
实现例子2:
拓展
当策略数量较多时,使用策略工厂模式,对策略进行管理
将工厂加载进程序,在后续的使用中就可以更加方便
“如何将方法的请求者与实现者解耦?”
看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式
电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)
定义
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
通过命令对象进行沟通
结构
理解:调用者去使用命令就行,不用关系命令怎么执行的,命令会去安排接受者完成动作
实现例子:
拓展
使用者可以通过组合模式,将命令作为一个组合—》宏命令
公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据需要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这无疑增加了难度。
每台计算机根据目标地址是否同自己的地址相同来决定是否接收
定义
避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止
理解
将所有的接受者组成一条链,请求输入这条链,这条链就会自动遍历所有的接受者,直到成功响应。
客户只需要将请求发送到这条链,怎么处理是这条链的事情
结构
在处理者的内部进行链式的连接,如果这个处理者无法处理或者出现异常,移动到下一个处理者
class ConcreteHandler1 extends Handler {
public void handleRequest(String request) {
if (request.equals("one")) {
System.out.println("具体处理者1负责处理该请求!");
} else {
if (getNext() != null) {
getNext().handleRequest(request);
} else {
System.out.println("没有人处理该请求!");
}
}
}
}
这个有点像链表的定义
应用实例
假如规定学生请假小于或等于 2 天,班主任可以批准;小于或等于 7 天,系主任可以批准;小于或等于 10 天,院长可以批准;其他情况不予批准;这个实例适合使用职责链模式实现。
人类是有状态的
- 高兴
- 伤心
这些都是内部状态
人类会根据不同的状态执行不用的动作
而且外部的状态也会引起内部情绪状态的变化
定义
对于有状态的对象,将复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在内部发生改变时改变行为
结构
实现例子
理解:
实现例子2
线程的使用是外部的使用带来的内部状态的变化
每一个状态都从初始抽象状态继承得到,但是根据实现步骤,每个状态都是具有独特的方法的。
而转换状态的方法是在每个状态内部的
比如从就绪—运行
public void getCPU() {
((Runnable) state).getCPU(this);
}
将state转成就绪状态来使用,使用后,内部状态被set
将自身作为变量传入,修改自身的状态
public void getCPU(ThreadContext hj) {
System.out.print("获得CPU时间-->");
if (stateName.equals("就绪状态")) {
hj.setState(new Running());
} else {
System.out.println("当前线程不是就绪状态,不能获取CPU.");
}
}
拓展
可以使用状态工厂进行状态的保存和使用
区别
状态模式—责任链模式
状态—策略
一个对象的行为改变会牵扯到其他一个或多个对象的行为改变
某种商品的物价上涨时会导致部分商家高兴,而消费者伤心
我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。
在软件世界中
数据和折线图进行绑定,模型和视图绑定
数据的流动
事件源和事件处理者
定义
多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式
结构
具体目标对象和具体观察者对象之间不能直接调用
理解
发布者能够接纳订阅者,并且能够通知所有订阅者,订阅者被通知后会采取响应措施,针对发布者的通知内容执行动作
应用案例
汇率的变化会影响不同公司在营销上的策略
应用案例–2
学校铃声会影响学生的活动
使用监听者模式—一样的处理
事件源处添加监听者,事件发生的时候,将发生的事件类型传给监听者,监听者根据事件的类型执行不同的动作
应用场景
当系统一方行为依赖另一方行为的变动时,可使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。
拓展
java中带有 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
使用:
public void setPrice(float price) {
super.setChanged(); //设置内部标志位,注明数据发生变化
super.notifyObservers(price); //通知观察者价格改变了
this.price = price;
}
中介者模式
好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”
每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须让其他所有的朋友一起修改,这叫作“牵一发而动全身”,非常复杂
使用中介者进行沟通,在网上建立一个每个朋友都可以访问的“通信录”就解决了
中介里面装的时对象之间的交互逻辑
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IQFt4trN-1667801274442)(D:\HONOR Magic-link\Huawei Share\Screenshot_20211210_145147.jpg)]
应用案例
房地产交流
拓展
使用单例模式产生中介,在使用对象创建时就把中介注入然后注册自身,这样就能省去一些步骤。
访问一个聚合对象“链表”之类的,如果要进行修改遍历的方法只能够修改本身,如果通过继承的方法新增也不现实,因为这样其他依赖其的方法也需要修改。
所以遍历方法不能放在聚合对象内部,在内部使用注入迭代器,通过调用迭代器的方法让外部能够访问
迭代器创建的时候就将数据装入,然后通过index进行索引,index尽量学会用-1作为初始值
应用例子
景色展示
拓展
使用迭代器对容器内的子组件进行遍历获取和访问,结合组合模式
集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。
医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。
不同人对处方的访问是不一样的
有些人访问的目的只是为了得到里面的价格
有些人访问是为了得到里面的位置
定义
把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离
理解
将对象的访问进行抽取成为一个针对于这个对象的访问类(Visitor类),外部对这个对象进行访问的时候,直接通过访问类进行访问,这样就不需要在对象里面专门新增一个给A或者B访问的方法。访问类会根据访问对象的类型来使用不用的访问方法得到目标的数据。
结构
主要角色:
抽象访问
具体访问
抽象元素 具有接受操作accept()操作,
具体元素 实现accept 方法体一般是visitor.visit(this)
对象结构 包含元素角色的容器
理解
元素中有一个方法accept指定观察者,观察者对这个元素自身进行访问
对于观察者来说,只能够去访问别人,那肯定有visit函数
对于元素,使用一个结构体+迭代器的方式,让访问者对里面的每个元素进行访问
在多对多的关系下
应用实例
很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
结构
理解
发起人将状态发送到备忘录,然后创建的备忘录交给管理者进行保存
public class MementoPattern {
public static void main(String[] args) {
Originator or = new Originator();
Caretaker cr = new Caretaker();
or.setState("S0");
System.out.println("初始状态:" + or.getState());
cr.setMemento(or.createMemento()); //保存状态
or.setState("S1");
System.out.println("新的状态:" + or.getState());
or.restoreMemento(cr.getMemento()); //恢复状态
System.out.println("恢复状态:" + or.getState());
}
}
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public Memento createMemento() {
return new Memento(state);
}
public void restoreMemento(Memento m) {
this.setState(m.getState());
}
}
发起人可以通过保存的备忘录的信息来替换当前的值
应用实例
使用栈进行管理
类似于撤回功能,返回上一个
拓展
使用原型模式将发起人转换成一个原型,使用原型管理器进行保存,那这个原型就相当于备忘录
每次不一样的原型都相当于一个不一样环境的骨架
有许多重复性的问题,这些问题在操作和使用上具有一定的相似性和规律性,那么可以自定义一种语言来定义这些操作,这些定义的语言经过解释器的处理就能够正常地执行
比如是在linux系统里面
我们可以指定
lls 对 原生语言 ll -a 进行代替