装饰器模式
参考文章:
《软件设计模式概述》
《设计模式之美》——极客时间·王争
一、概念
我们有一个文件读取功能,可以读取磁盘上的文本类文件、音乐类文件以及视频类文件。
后来,我们觉得读取速度很慢,与磁盘交互太多,想将现有的读取功能增强,增加缓冲区。
FileRead功能(Old) | FileRead功能(New) |
---|---|
![]() | ![]() |
这种改进措施有很大弊端,复用性很低,需要改进。
改进后结构图 | ![]() |
---|---|
装饰器模式结构图(简约版) | ![]() |
装饰器设计模式:
通过创建一个包装对象,也就是装饰来包裹真实的对象,从而增强一个类的功能
角色说明:
1、Component 是统一接口,也是装饰类和被装饰类的基本类型
2、ConreteComponent 是具体实现类,也是被装饰类,本身就是个具有一些功能的完整类
3、ConcreteDecorator是具体的装饰产品类,实现了Component接口的同时还在内部维护了一个Component实例,通过构造函数初始化,每一种装饰产品都具有特定的装饰效果,可以通过构造器来声明装饰哪种类型Component,从而对其装饰。
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74//ReadFile抽象类 public abstract class FileRead { public abstract void read(); } //读取文本文件类 public class FileTextRead extends FileRead { String name = "文本"; @Override public void read() { System.out.println(name + "文件直接读取....."); } } //读取音乐文件类 public class FileMusicRead extends FileRead { String name = "音乐"; @Override public void read() { System.out.println(name + "文件直接读取......."); } } //读取视频文件类 public class FileVedioRead extends FileRead { String name = "视频"; @Override public void read() { System.out.println(name + "文件直接读取,......"); } } /** * 缓冲类,同样继承ReadFile类,但是里面创建了父类对象,可以传入其三个子类对象 * 实现了三个子类各自功能的同时还增加了缓冲机制 */ public class BufferdFileRead extends FileRead { private FileRead fileRead; //这里会实现多态 public BufferdFileRead(FileRead fileRead) { this.fileRead = fileRead; } @Override public void read() { //这个read方法是缓冲类自己的read //这个read是传入的FileRead类对象或者其子类对象的read方法 fileRead.read(); System.out.println("缓存。。。。。"); } } //测试 public class test { public static void main(String[] args) { FileTextRead fileTextRead = new FileTextRead(); FileMusicRead fileMusicRead = new FileMusicRead(); FileVedioRead fileVedioRead = new FileVedioRead(); new BufferdFileRead(fileTextRead).read(); new BufferdFileRead(fileMusicRead).read(); new BufferdFileRead(fileVedioRead).read(); } } //执行结果 文本文件直接读取..... 缓存。。。。。 音乐文件直接读取....... 缓存。。。。。 视频文件直接读取,...... 缓存。。。。。
二、jdk的IO流
InputStream输入流 | ![]() |
---|---|
标准装饰器模式结构 | ![]() |
为何我们需要一个抽象装饰器类?
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
29public class BufferedInputStream extends InputStream { /** * The input stream to be filtered. */ protected volatile InputStream in; /** * 构造方法 */ protected FilterInputStream(InputStream in) { this.in = in; } /** * 装饰代码,增强功能 */ public void f() { //功能增强代码 in.f(); } /** * 其他不需要增强的方法,重新调用一下InputStream中的方法,否则无法将最终读取数据的任务委派给传递进来的InputStream对象 */ public void function() { in.fiunction(); } }
为了避免对不需要增强功能的方法的代码进行重复书写,javaIO抽象出了一个装饰器父类FilterInputStream,所有的装饰器类都继承它,这样,装饰器只需要实现它需要增强的方法即可,其他只需要实现父类的默认实现。
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
39
40
41
42
43
44
45
46
47
48
49
50> public class FilterInputStream extends InputStream { > /** > * The input stream to be filtered. > */ > protected volatile InputStream in; > > /** > * 构造方法 > */ > protected FilterInputStream(InputStream in) { > this.in = in; > } > > public int read() throws IOException { > return in.read(); > } > > public int read(byte b[]) throws IOException { > return read(b, 0, b.length); > } > > public int read(byte b[], int off, int len) throws IOException { > return in.read(b, off, len); > } > > public long skip(long n) throws IOException { > return in.skip(n); > } > > public int available() throws IOException { > return in.available(); > } > > public void close() throws IOException { > in.close(); > } > > public synchronized void mark(int readlimit) { > in.mark(readlimit); > } > > public synchronized void reset() throws IOException { > in.reset(); > } > > public boolean markSupported() { > return in.markSupported(); > } > }
三、装饰器模式拓展
装饰模式所包含的 4 个角色不是任何时候都要存在的,在有些应用环境下模式是可以简化的。
装饰类和具体的装饰产品类合并 | 只有一个具体实现类而没有统一的抽象接口 |
---|---|
![]() | ![]() |
四、优缺点
优点:装饰类和被装饰类可以独立发展,不会相互耦合,不必改变原类文件和继承的情况下,动态地扩展、增强一个对象的功能。原始类对调用方是透明的,可以根据情况灵活使用。
缺点:多层装饰比较复杂
1
2BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(new File("D:/Java/Blog/Demo.txt")));
五、对比
代理、适配器、桥接、装饰,这四个模式有何区别
这四种模式都是比较常用的结构型设计模式,代码结构也是很类似,都是二次封装原始类。它们的主要区别就在于使用目的、要解决的问题以及使用的场景不同。
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
33public interface CommonService { /** 方法 */ void function(); } public class AServiceImpl implements CommonService { /** A的方法实现 */ @Override public void function() { System.out.println("执行A的方法"); } } public class BServiceImpl implements CommonService { /** 实现的公共接口 */ private CommonService commonService; /** 构造方法 */ public BServiceImpl(CommonService commonService) { this.commonService = commonService; } /** 执行B的方法 */ @Override public void function() { System.out.println("执行B所需要的其他代码......."); commonService.function(); } }
1、代理模式:不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,附加其他功能,而非增强功能,对调用者而言是不可见的。
2、桥接模式:目的是将接口部分和实现部分分离,从而让他们解耦,可以较为容易、相对独立的进行改变和拓展
3、装饰器模式:不改变原始接口类的条件下,对原始类的原有功能进行增强
4、适配器模式:提供跟原始类不同的接口,适配了两种具有不同接口(API)的对象,可以一同工作
代理模式 | ![]() | Client是房东,Proxy是蛋壳,target是租户 |
---|---|---|
桥接模式 | ![]() | 目的是将接口部分和实现部分分离,从而让他们解耦,可以较为容易、相对独立的进行改变和拓展;先定义了一个桥(即两个接口之间的关系),然后通过每个接口的多个实现的不同组合达到其灵活性的目的,即先关系后组合;组合多个维度变化的接口,比如多个品牌电脑和多个电脑的功能 |
装饰器模式 | ![]() | Target租户自己,自己冒冷天,骑车、花钱打车到处乱转找房子,自己协商,自己考察房子情况和业主情况,自己交水电,自己办网,自己打扫。。。。 |
适配器模式 | ![]() | 提供跟原始类不同的接口,适配了两种具有不同接口(API)的对象,可以一同工作;你提供接口A,但是客户需要的是接口B,那么我们就需要将A变成B |
外观模式
一、概念
![]() | ![]() |
---|---|
把医院作为一个子系统,按照部门职能,这个系统可以划分为挂号、门诊、划价、化验、收费、取药等。看病的病人要与这些部门打交道,就如同一个子系统的客户端与一个子系统的各个类打交道一样,不是一件容易的事情 | 医院可以设置一个接待员的位置,由接待员负责代为挂号、划价、缴费、取药等。这个接待员就是门面模式的体现,病人只接触接待员,由接待员与各个部门打交道 |
外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,相对于外部应用程序,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
二、模式结构和角色
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
- 客户(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
39
40
41
42
43
44
45
46
47package facade; public class FacadePattern { public static void main(String[] args) { Facade f=new Facade(); f.method(); } } //外观角色 class Facade { private SubSystem01 obj1=new SubSystem01(); private SubSystem02 obj2=new SubSystem02(); private SubSystem03 obj3=new SubSystem03(); public void method() { obj1.method1(); obj2.method2(); obj3.method3(); } } //子系统角色 class SubSystem01 { public void method1() { System.out.println("子系统01的method1()被调用!"); } } //子系统角色 class SubSystem02 { public void method2() { System.out.println("子系统02的method2()被调用!"); } } //子系统角色 class SubSystem03 { public void method3() { System.out.println("子系统03的method3()被调用!"); } }
子系统01的method1()被调用!
子系统02的method2()被调用!
子系统03的method3()被调用!
四、应用场景
在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。
通常在以下情况下可以考虑使用外观模式。
解决分布式事务问题:连表多表操作,提供接口,执行两个sql,注释为事务注释
复制代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/** * 事务:批量入从表,同时修改主表状态为同步中 * @param policyFollows 从表数据 * @param policy 主表 */ @Override @Transactional(rollbackFor = Exception.class) public void insertPolicyFollowsAndUpdateParentStatus(List<updatePolicyFollow> policyFollows, UpdatePolicy updatePolicy) { /*批量插入从表*/ scheduledTaskManager.insertUpdatePolicyFollows(policyFollows); /*设置主表状态*/ updatePolicy.setSyncStatus(Byte.valueOf(SyncStatusEnum.SYNCING.getCode())); updatePolicy.setRemark(SyncStatusEnum.SYNCING.getDesc()); updateParentStatus(updatePolicy); }
解决性能问题,将多个调用接口替换为一个门面接口,减少网络通信成本
解决易用性问题,当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问,隐藏系统的复杂性,并将其分离,提高子系统的独立性和可移植性,比如linux的复杂内核和shell命令
五、优缺点
外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
缺点:
增加新的子系统可能需要修改外观类或客户端的源代码,在一定程度上违背了“开闭原则”
外观模式拓展:引入抽象外观类,则在一定程度上解决“开闭原则”问题
拓展结构 |
---|
![]() |
六、与适配器的区别
两者共同点是都将不好用的接口适配成好用的接口。
不同点:
适配器是做接口转换,解决的是原接口和目标接口不匹配的问题。
门面模式做接口整合,解决的是多接口调用带来的问题
最后
以上就是健壮水蜜桃最近收集整理的关于装饰器和外观设计模式的全部内容,更多相关装饰器和外观设计模式内容请搜索靠谱客的其他文章。
发表评论 取消回复