设计模式系列
创建型设计模式
Java 设计模式之静态工厂、工厂方法、抽象工厂和 Builder 模式的区别
结构型设计模式
前言
朋友们,玩过给小伙伴穿衣服的游戏吗?😏
你以为你穿上的是衣服吗?不,那是装饰者!你以为那是你的小伙伴吗?不,他是被装饰者!
来吧,我们大家一起玩。😀
定义
装饰者模式(Decorator Pattern),又被称为包装模式(Wrapper Pattern),是我们要学习的结构型设计模式的第三篇,它可以在不改变原有对象代码结构的情况下,动态为原有对象添加一些额外的职责,被认为是继承关系的一种替代方案。
我从书上截了一张装饰者模式的 UML 图:
让我们一起来理解一下:
Component:抽象组件,可以是一个接口或者抽象类,用来充当被装饰者的原始对象。
ConcreteComponent:抽象组件的实现类,是要装饰的具体对象。
Decorator:装饰者的抽象组件,其内部拥有一个指向Component
的引用,用来对具体对象ConcreteComponent
进行装饰。在装饰逻辑单一的情况下,我们可以省略掉此抽象组件,使用一个非抽象的装饰类直接对ConcreteComponent
进行装饰,如直接使用图中所示的ConcreteDecoratorA
或者ConcreteDecoratorB
。
ConcreteDecoratorA:具体的装饰类,提供某些装饰功能,对具体的被装饰对象ConcreteComponent
进行装饰,也可以对其进行功能增强。
说起功能增强,我们之前学习过的 代理模式 也可以。装饰者模式和代理模式,是容易混淆的两种模式,稍后我们再专门对他们对比分析。
ConcreteDecoratorB:另一个具体的装饰类,与ConcreteDecoratorA
类似。
示例
来吧,代码上见真章!我们这就来给小伙伴穿衣服。
UML类图
-
Person:抽象组件,是被装饰的原始对象,声明了
dress()
的基础行为。 -
Boy:我们的有血有肉的小伙伴,我们就是要给他穿衣服。
Boy
小伙伴实现了dress()
的行为。他自觉地穿上了内衣,作为他的dress()
行为的基本实现,装饰者要做的,就是对此dress()
行为进行重写或者增强。 -
ClothesWrapper:装饰者的抽象类,持有对
Person
的引用。 -
ShirtWrapper:衬衣装饰类,提供给
Person
穿衬衣的装饰功能。 -
CoatWrapper:外套装饰类,提供给
Person
穿外套的装饰功能。
注意:我们可以为Person
提供穿衬衣或者穿外套的装饰功能,也可以为其提供穿衬衣和穿外套的装饰功能。也就是说,我们的装饰类可以单一使用,也可以叠加使用。
程序代码
源码在这里:Github
1、抽象类Person
,声明了行为dress()
:
public abstract class Person {
public abstract String dress();
}
2、具体的被装饰类Boy
,其具备dress()
行为的基础实现——“穿内衣”:
public class Boy extends Person {
@Override
public String dress() {
return "穿了衣服:内衣 ";
}
}
3、装饰抽象类ClothesWrapper
,持有对Person
的引用,调用了其基础的穿内衣行为:
/**
* 衣服包装的抽象类
*/
public abstract class ClothesWrapper extends Person {
private Person mPerson;
public ClothesWrapper(Person person) {
this.mPerson = person;
}
@Override
public String dress() {
return mPerson.dress();
}
}
4、具体的装饰类ShirtWrapper
:
/**
* 衬衣包装类
*/
public class ShirtWrapper extends ClothesWrapper {
public ShirtWrapper(Person person) {
super(person);
}
@Override
public String dress() {
return super.dress() + "衬衣 ";
}
}
5、具体的装饰类CoatWrapper
:
/**
* 外套包装类
*/
public class CoatWrapper extends ClothesWrapper {
public CoatWrapper(Person person) {
super(person);
}
@Override
public String dress() {
return super.dress() + "外套 ";
}
}
6、调用方Main
:
public class Main {
public static void main(String[] args) {
// 基础穿着
Person tom = new Boy();
System.out.println(tom.dress());
// 基础穿着 + 衬衣
ShirtWrapper shirtWrapper = new ShirtWrapper(tom);
System.out.println(shirtWrapper.dress());
// 基础穿着 + 外套
CoatWrapper coatWrapper = new CoatWrapper(tom);
System.out.println(coatWrapper.dress());
// 基础穿着 + 衬衣 + 外套(装饰者叠加/嵌套使用)
CoatWrapper fullDress = new CoatWrapper(shirtWrapper);
System.out.println(fullDress.dress());
}
}
程序运行结果:
从上述示例可以看出来,装饰者模式是很好理解的,接下来我们来梳理下装饰模式的应用场景。
应用场景
-
在不方便,或者不可以使用继承的情况下,对原对象进行功能扩展时,我们可以选用装饰者模式,因此其常作为继承的替代方案;
-
其可以在不改变原对象的代码及继承、实现关系的情况下,对原对象进行功能扩展,这也更加符合“面向修改关闭,面向扩展开放”的设计原则;
-
通过其为原对象增加的扩展功能,既可以新增,也可以方便的撤销,对原代码不会产生任何影响;
-
当我们想要对老代码老模块进行功能改写或者扩展,但又不敢或者不愿意动老代码时,我们可以采用装饰者模式。
从这些应用场景中,我们也不难发现装饰者模式的优势。
优势
-
用装饰者模式替代继承,可以避免为了继承行为扩展各种各样的功能,而导致子类数量爆炸的问题;
-
装饰者模式,是采用组合的方式一次或者叠加多次对原对象功能进行扩展,使用组合方式而不是采用继承方式,可以大大提高灵活性,需要什么装饰功能就选用什么装饰器,又可以方便地撤销装饰;
-
原对象与装饰者,是相互独立地,装饰者的变化不会对原对象产生影响,这符合“面向修改关闭,面向扩展开放”的设计原则。
那使用装饰者模式有没有需要小心的地方,或者说它有什么劣势吗?
劣势
-
从 UML 图中可以看到,所有的类都继承自抽象组件
Component
,一旦Component
中的行为发生变化,将会对所有实现类包括装饰器的实现类产生影响; -
要避免装饰器叠加嵌套层级过多,一旦出现问题,要确定问题来源于哪一层,将比较困难。
Android源码中的装饰者模式
在Android开发者的日常工作中,最常见的装饰者模式的例子就是大名鼎鼎的Context
,Context
在Android中的应用非常非常普遍。
我们还是以 UML 图的形式展示装饰者模式在Context
中的应用吧。
我们来简单梳理一下:
-
Context:抽象组件,是被装饰的原始对象。
其中声明了大量的与应用程序息息相关的重要的方法,这些方法不但与我们常说的四大组件相关,还与资源文件、文件管理、包管理、类加载、权限管理、系统级服务获取等各式各样的功能相切合。
-
ContextImpl:抽象组件的实现类,实现了上述的各种重要的方法。
-
ContextWrapper:装饰类的抽象组件,其中持有对
Context
的引用。ContextWrapper
的所有方法实际上也仅仅是通过mBase
简单地调用了ContextImpl
中的方法,至于要通过装饰者对上述方法的功能增强,还是在其子类中完成的。而我们所熟知的
Activity
、Service
、Application
都是ContextWrapper
的子类。
总结
装饰者模式是一种好用又易于理解的设计模式。整篇文章看下来,相信大家都体会到装饰者模式最重要的作用就在于其对原有对象的功能增强,又可以不改变原有对象的代码了。
下篇文章,我们一起来学习桥接模式。代理模式、装饰者模式、桥接模式有一些相似,我们将在之后对他们进行对比。
感谢
- 《Android源码设计模式解析与实战》 何红辉 关爱民
- 《Android进阶之光》 刘望舒
- 《Head First 设计模式》 [美] Freeman等
- 在线绘图网站 ProcessOn