Java设计模式之装饰模式
前言
什么是装饰模式呢?装饰模式是指动态地给一个对象添加额外的职责。因此装饰,也叫对象的包装。
装饰模式示例
互联网行业的迅猛发展,涌现了不计其数的Java开发工程师,想想必大家都很清楚,需求有产品经理把控,设计和开发一般是开发人员开发。软件测试由测试工程师负责。部署上线一般来说是实施人员,但是有很多公司自用的系统,往往是由项目负责人负责上线的工作。我早年大学还没毕业,在江苏一家公司工作,我记得当时我们的整套软件产品都是由我们的技术负责人上线。而且一般来说项目的开发一般有周期,同时分阶段。那就涉及到线上环境版本更新的频率,一般,一周或两周更新依次版本的。
我们就以项目部署上线来阐述装饰模式。部署上线,核心的流程有:
1.拿到测试人员的软件测试质量报告。
2.根据测试报告从下游系统开始依次往上进行升级。
于是我们首先来创建一个抽象类,用来定义项目版本更新的流程。我就命名为AbstractCompose.java。代码如下所示:
1 package com.example.pattern.decorator; 2 3 public abstract class AbstractCompose { 4 5 public abstract void report(); 6 7 public abstract void update(); 8 }
第5行:拿到测试人员测试通过的质量报告。
第7行:线上环境项目版本更新。
既然有抽象类,我们再来看具体执行的流程,这个流程的实现类继承自 流程抽象类 代码如下所示:
1 package com.example.pattern.decorator; 2 3 public class PublicCompose extends AbstractCompose { 4 @Override 5 public void report() { 6 System.out.println("测试通过的测试质量报告"); 7 } 8 9 @Override 10 public void update() { 11 System.out.println("项目更新到最新版本"); 12 13 } 14 }
接下来,是我们的开发人员,对这个流程执行。因此我们定义一个Developer作为开发者,这个开发者的角色是也是我们习惯所说的场景类。
1 public class Developer { 2 3 public static void main(String[] args) { 4 AbstractCompose compose = new PublicCompose(); 5 compose.report(); 6 compose.update(); 7 } 8 }
这段代码特别的简单,也是对项目版本更新这段流程的直接表达。我们来看一下执行结果:
测试通过的测试质量报告
项目更新到最新版本
但是因为开发者个性的差异,比如有些开发者在执行版本更新之前,需要去一下wc。执行版本更新之后,需要团队聚餐。那好办,找一个子类来继承PublicCompose。
1 public class PublicDetailCompose extends PublicCompose { 2 3 4 public void toilet() { 5 System.out.println("开发小哥在WC"); 6 } 7 8 public void dinner() { 9 System.out.println("大家在共进晚餐"); 10 } 11 12 @Override 13 public void update() { 14 toilet(); 15 super.update(); 16 dinner(); 17 } 18 }
第13行-17行,通过重写父类的update方法,同时在调用父类的方法前后,修饰上WC和聚餐的动作。那么,开发小哥,也就是场景类需要做一下改动:
1 public class Developer { 2 3 public static void main(String[] args) { 4 AbstractCompose compose = new PublicDetailCompose(); 5 compose.report(); 6 compose.update(); 7 } 8 }
我们再执行一下,效果如下所示:
1 测试通过的测试质量报告
2 开发小哥在WC
3 项目更新到最新版本
4 大家在共进晚餐
第2行和第4行,装饰到我们的uodate方法中。
继承确实能够去解决一些问题。但是也出现了一些问题:
1.开发小哥WC可能在拿到测试报告之前,或者是在更新完之后。
2.聚餐可能发生在更新之前,也有可能在报告之前。
3.而WC这个流程节点,根据开发者的喜好不同,又可以分成两类,第一类:有的开发者喜欢WC之后洗手,第二类:有的开发者喜欢WC后不洗手直接回办公室等等。
这么多的场景,继续增加子类继承来扩展,于是出现了类的爆炸,类的数量激增。在面向对象语言 的开发中,如果继承的数量超过两层就应该要去考虑设计是不是合理。那有什么办法解决吗,有方法,我们来定义另一个装饰的接口,专门用于描述装饰的动作。我们先定义一个装饰的抽象类。这个抽象类必须要继承流程抽象类AbstractCompose。
1 public abstract class AbstractDecorator extends AbstractCompose{ 2 3 private AbstractCompose compose; 4 5 public AbstractDecorator(AbstractCompose compose) { 6 this.compose = compose; 7 } 8 9 @Override 10 public void report() { 11 this.compose.report(); 12 } 13 14 15 @Override 16 public void update() { 17 this.compose.update(); 18 } 19 }
第1行,这个装饰的抽象类,必须要继承流程的抽象类AbstractCompose。
第3行,必须要持有流程类的引用。
第10行-18行,还是委托给想要装饰的类的方法。report和update。
AbstractDecorator抽象类存在有什么意义呢?就是希望让AbstractDecorator的子类来封装和装饰我们 要去装饰的那一个类,也就是PublicCompose。
①首先封装一个wc的装饰器:
1 public class PublicToiletDecorator extends AbstractDecorator { 2 3 4 public PublicToiletDecorator(AbstractCompose compose) { 5 super(compose); 6 } 7 8 public void toilet () { 9 System.out.println("开发小哥在WC,并且没有洗手回到办公室 "); 10 } 11 12 13 @Override 14 public void update() { 15 toilet(); 16 super.update(); 17 } 18 }
第14行,override了update方法,同时在这个方法之前,增加了要装饰的方法toilet。
②我们再来创建一个聚餐的装饰类:
1 public class PublicDinnerDecorator extends AbstractDecorator { 2 3 4 public PublicDinnerDecorator(AbstractCompose compose) { 5 super(compose); 6 } 7 8 public void dinner () { 9 System.out.println("团队一起聚餐 "); 10 } 11 12 13 @Override 14 public void update() { 15 super.update(); 16 dinner(); 17 } 18 }
第15行,override了update方法,同时在这个方法之后,增加了要装饰的方法dinner。
我们先执行一下:
1 测试通过的测试质量报告
2 开发小哥在WC,并且没有洗手回到办公室
3 项目更新到最新版本
4 团队一起聚餐
是不是很神奇,如果想继续装饰其他的行为,是不是就变得比较简单了。
装饰模式的角色
1.想要装饰的类的抽象或者接口A
2.具体的装饰类B,这个类继承抽象类,或者实现接口A
3.抽象的装饰接口或者抽象类C,同时继承或者实现A.同时持有A的引用
4.具体的装饰类。这个类继承或者实现自C
装饰模式的优点和缺点
1.装饰类和被装饰类相互独立。
2.装饰模式是继承的一种代替方案。
3.装饰模式可以动态扩展一个类的功能和行为。
以上是装饰模式的优点,同时在装饰层数过多时,排查问题比较复杂。因此,尽量减少装饰类的数量来降低系统的复杂度。