装饰模式介绍
装饰模式(Decorator Pattern)是结构型设计模式之一,其可以在不改变类文件和使用继承的情况下,动态地扩展一个对象的功能,是继承的替代方案之一。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
装饰模式定义
通过包装一个对象,动态地给给该对象添加一些额外的职责。
装饰模式使用场景
- 需要透明且动态地扩展类的功能时。
- 部分功能需要动态添加,或动态撤销时。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
装饰模式的 UML 类图
角色介绍:
- Component:抽象组件。可以是接口或抽象类,其充当的是被装饰的原始对象。
- ConcreteComponent:组件具体实现类,也就是我们具体的被装饰的对象。
- Decorator:抽象装饰者。其内部一定要有指向组件的一个引用,但对于Component来说无需知道Decorator的存在。
- ConcreteDecorator:装饰者的具体实现类。
装饰模式的简单实现
这里以绘制图形为例,图形只需要负责绘制自己,对于其他颜色,边框粗细可以交由装饰者完成。
抽象组件(Component)
定义图形绘制的抽象类,这里定义为接口
public interface Shape {
void draw();
}
组件具体实现类(ConcreteComponent)
矩形绘制实现类
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Rectangle");
}
圆形绘制实现类
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
抽象装饰者(Decorator)
抽象装饰者实现组件接口,且需要绘制接口的引用指向具体的组件实现类
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape) {
this.decoratedShape = decoratedShape;
}
@Override
public void draw() {
decoratedShape.draw();
}
}
装饰者具体实现类(ConcreteDecorator)
例如需要在绘制时进行红色涂色
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
// 先绘制
decoratedShape.draw();
// 涂红色
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape) {
System.out.println("Border Color: Red");
}
}
客户端调用
public class Client {
public static void main(String[] args) {
Shape circle = new Circle();
ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
circle.draw();
redCircle.draw();
redRectangle.draw();
}
}
输出结果如下:
Shape: Circle
Shape: Circle
Border Color: Red
Shape: Rectangle
Border Color: Red
可见装饰者再不改变原有类的情况下实现类具体组件的功能拓展。
总结
装饰器模式的优点
- 装饰类和被装饰类可以独立发展,不会相互耦合,换句话说,Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。装饰模式是继承关系的一个替代方案。
- 装饰器可以动态开启或撤销
装饰器模式的缺点
- 装饰层级不宜过多,否则代码更容易出错,且调试困难。
装饰模式和代理模式区别
代理模式和装饰模式有点像,都是持有了被代理或者被装饰对象的引用。它们两个最大的不同就是装饰模式对引用的对象增加了功能,而代理模式只是对引用对象进行了控制却没有对引用对象本身增加功能。
装饰模式的使用场景
- 需要扩展一个类的功能,或给一个类增加附加功能。
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
- 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式。
Android 源码中的装饰模式
Android Context 可以说是上帝对象,好像没了她什么也干不了,比如启动Activity,Service,获取app目录,获取资源文件等等。
那么 Context 是什么?到底和 Activity ,Service ,Application 存在怎样的联系。说实话有些中级工程师也不明白,其实 Context 就是装饰者模式的抽象组件。
public abstract class Context {
public abstract void startActivity(Intent intent);
public abstract ComponentName startService(Intent service);
public abstract SharedPreferences getSharedPreferences(String name, int mode);
...
}
而真正的实现是在 ContextImpl 中完成的,ContextImpl 继承自 Context。
class ContextImpl extends Context {
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
...
}
ContextImpl 对抽象组件 Context 的方法进行了实现,其就是组件的具体实现类。那么谁来承担装饰者的身份?我们知道 Activity 本质上是一个 Context,Activity 并没有继承自 Context,而是继承自 ContextThemeWrapper。
public class Activity extends ContextThemeWrapper {
...
}
ContextThemeWrapper 又继承自 ContextWrapper。
public class ContextThemeWrapper extends ContextWrapper {
...
}
ContextWrapper 才继承自 Context,为什么类结构这么复杂?其实这就是一个典型的装饰模式,装饰模式又称为包装模式(WrapperPattern),这个类名也印证了装饰模式。
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
...
}
ContextWrapper 中存在一个 Context 对象的引用,且存在抽象组件的方法,可见ContextWrapper 就是名副其实的装饰者。startActivity 调用了 mBase的startActivity 方法,实际上 ContextWrapper 中的所有方法仅仅是调用了 ContextImpl 的对应方法。对于具体的装饰者则是由 ContextWrapper 的具体子类完成,比如我们的 Activity、Service 和 Application。
但当我们查看 Activity、Service 和 Appliation 源码是会发现基本上所有方法并没有实现对组件方法的扩展,而是做了重写或者直接使用具体组件的实现。
public class Activity extends ContextThemeWrapper {
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
startActivityForResult(intent, -1);
}
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
// 调用 mInstrumentation.execStartActivity 启动 Activity
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
...
} else {
...
}
}
...
}
以 startActivity 为例,Activity 执行启动 Activity 方法最终是通过 mInstrumentation.execStartActivity
完成,而并非 ContextImpl ,那这还是装饰模式吗?答案是是,整体继承关系符合装饰模式,对于具体方法可以根据实际情况处理。