背景
这是我第二次写装饰模式,这一次是在上一次的基础上进一步探究装饰模式,这一次有了很多新的感受和想法,也多了很多趣味性的内容,读完一定让你觉得写代码原来这么有意思。还是简单介绍一下装饰模式的概念,如果想了解更基础更详细的内容,可以先读一下我的第一篇装饰模式的博客:设计模式之装饰模式–优雅的增强
概述
概念
装饰模式(Decorator Pattern)是一种结构型设计模式,动态地给一个对象添加一些额外的功能,就增加功能来说,装饰模式比生成子类更为灵活。
这张图今天将反复出现,先来对照概念来一一认识一下这上面的每一个类
角色
在装饰模式中,通常涉及以下几个重要角色(请对照概念和类图):
- 抽象组件(Component)
抽象组件定义了被装饰对象和装饰对象的共同接口。 - 具体组件(Concrete Component)
具体组件是实现了抽象组件接口的类,它是装饰的目标对象(要增加额外职能的类)。 - 抽象装饰器(Decorator)
抽象装饰器是一个包含了与抽象组件相同接口的成员变量,并从抽象组件继承的类。它的职责是在调用原始对象之前或之后,执行额外的操作(增加的额外职能的父类)。 - 具体装饰器(Concrete Decorator)
具体装饰器是实现了抽象装饰器接口的类,它包装了具体组件,并可以在调用前后执行额外的操作(增加的具体的额外职能)。
总结一下:这种图昨天的类是要增加额外职能的类,右边一坨是要增加的额外职能。
打个比方,左边的 具体组件(Concrete Component)和右边要增加的职能抽象装饰器(Decorator)本来毫不相干,但是现在非要把它们往一起凑(非要变成一家人),怎么办,那就找了一个义父(抽象组件(Component)),强硬的变成了一家人(同一个义父的子类)
基本代码分析
抽象组件:(定义的规范:子类如何实现的规范)
public interface Component { void operation(); }
具体组件:
public class ConcreteComponent implements Component { @Override public void operation() { System.out.println("具体组件的操作"); } }
抽象装饰器:
这是关键类,里面一共三部分,一个Component 类型的属性,一个setComponent方法,用来给属性赋值,这里使用了多态,参数Component component,看起来接受的是一个Component 类型的变量,但是自始至终,接受的只是Component 的子类(ConcreteComponent )和孙类(ConcreteDecoratorA 和ConcreteDecoratorB),用两个词语里形容就是 认贼作父和认孙做父!这个放到后面细说。第三部分是重写的父类方法,调的是传进来的Component类型的变量的operation方法。
public abstract class Decorator implements Component { protected Component component; public void setComponent(Component component) { this.component = component; } @Override public void operation() { if (component != null) { component.operation(); } } }
具体装饰器A:
这两个具体的装饰类继承自Decorator ,同时也继承自Component (双层继承),这里的重点是operation方法中的super.operation();也就是这个方法让整个装饰的过程按照正确顺序串联起来。
ConcreteDecoratorA 中增加了一个属性,也就是给被装饰对象增加的额外职责。
ConcreteDecoratorB中增加了一个方法,给给装饰对象增加的额外职责。
这两个类算是从两个维度,告诉我们如何给被装饰对象增加额外职责的。
public class ConcreteDecoratorA extends Decorator { private String addedProperty; @Override public void operation() { super.operation(); addedProperty="New State"; System.out.println("具体装饰器A的操作); } }
具体装饰器B:
public class ConcreteDecoratorB extends Decorator { @Override public void operation() { super.operation(); addedMethod(); } public void addedMethod() { System.out.println("具体装饰器B的操作"); } }
客户端使用:
ConcreteComponent c= new ConcreteComponent(); ConcreteDecoratorA d1= new ConcreteDecoratorA(); ConcreteDecoratorB d2= new ConcreteDecoratorB(); d1.setComponent(c); d2.setComponent(d1); ConcreteDecoratorB.operation();
客户端里先实例化了三个对象
第一对象C是要装饰的原有对象。
后面两个对象是具体的装饰对象。
重点来了:我来说一下下面代码的执行顺序,不要眨眼**
d1.setComponent ( c ) 这句代码执行的时候会先到ConcreteDecoratorA 类中,没有找到setComponent 方法,因此会向它的父类Decorator 中找,然后把C对象传递给Decorator 的component属性
同样,d2.setComponent(d1)执行的时候,会把装饰了C对象的d1对象传递给Decorator 的component属性。这里一定要注意是装饰了C对象的d1对象。
接下来,执行ConcreteDecoratorB.operation();
会先到ConcreteDecoratorB找到operation方法,这个方法里的第一句是super.operation();
是先执行父类Decorator 的operation方法,这个方法里先判空,然后执行component的operation,这个时候要注意,这个component是刚刚上面传进来的d1,
此时会d1是ConcreteDecoratorA 类型的,那又回到了ConcreteDecoratorA 中执行operation方法,接下来和刚才一样,执行父类Decorator 的operation方法,判空,然后执行component的operation,这个component是刚刚上面传进来的c,c是ConcreteComponent 类型,然后执行ConcreteComponent 里面的operation方法,打印“具体组件的操作”,后面就是一路返回,怎么来的怎么回去。
输出结果为:
具体组件的操作 具体装饰器A的操作 具体装饰器B的操作
❀❀花样重难点
聚合关系
还是这张图
红框框住的两部分,被装饰对象和具体装饰对象加起来是Component (儿子和孙子的抽象),也就是聚合关系的箭头所指,他们加起来聚合到了Decorator ,这是形式上的聚合,在代码中并没有体现(关于这一点还有待探究)
那么为什么不是Decorator自聚合呢,如果是自聚合,只是代表Decorator是具体装饰的容器,那么就不能把服饰包装到ConcreteComponent 上了
关于上面的解释在代码中的体现就是Decorator类中的Component 类型的参数
这里引出来我的下一个问题
认贼作父和认孙做父
Component 本来是Decorator的父类,但是在代码的实际运行中,传的只有ConcreteComponent 和ConcreteDecoratorA 和ConcreteDecoratorB。
ConcreteComponent 相对于Decorator来说是同辈份。(认贼作父)
ConcreteDecoratorA 和ConcreteDecoratorB都是Decorator的子类。(认孙做父)
客户端的优化及好处
这三段代码,可以看到引用都是子类型的,其实完全是可以用父类型去接
先问个问题,这三个实例都可以使用Component去接吗?
不要被迷惑住了,虽然他们都继承自Component,但是Component里可是没有setComponent方法的,所以只有new ConcreteComponent()可以用Component接。
代码可以改成:
Component c= new ConcreteComponent(); Decorator d1= new ConcreteDecoratorA(); Decorator d2= new ConcreteDecoratorB();
这样写有什么好处呢?
多客户端的复用
比如后两句都用Decorator 去接,=号右边完全可以使用配置文件,动态new对象,如果把没一句都看成一个按钮背后的逻辑,每个按钮都是一个客户端,放到页面上就实现了并行。重复的代码就可以使用自动生成。
继承到设计模式的演变过程
最后再说一个我自己的想法,仅供参考
还记得开头概念中说过的“就增加功能来说,装饰模式比生成子类更为灵活”,就先来说说继承,如果我们想给一个类增加功能,如果不在原有类中增加代码的话,可以使用继承的形式增加新的代码,那先看下面的图中左边的图,这个例子是设计模式书上的给人穿衣服的例子,如果不清楚,可以参照我的上一篇博客中的代码,链接在最上面。
加入TShirt是要给Person增加的功能,那么可以使用继承。左边的图。
那么根据封装变化点(这是面向对象的精髓),现在是增加了一个TShirt,我又想增加一个其他的类TShirt2,你会怎么做,会抽出一个父类,遵循依赖倒置原则,会让Person和接口Finery产生关系,就变成了右边的图
同样的道理,现在被装扮类不止一个Person,我也想给Animal装扮。那么就给Person和Animal抽出一个父类Component,遵循依赖倒置原则,会让Finery和接口Component产生关系,就变成了右边的图。现在看这张图和我开篇放的那张装饰模式的图是不是基本一致了,只是缺少了聚合关系。
关于聚合关系,在业务实现过程中并没有体现,这点以后有机会再研究。
总结
装饰模式算比较难的设计模式了,进行了多次学习,每次学习都有不一样的收获,而且越来越有意思,希望今天的文章也能带给你同样的感受。如果你也有有意思的想法和不同意见,欢迎指教,最后感谢阅读。