设计模式知识梳理(4) - 结构型 - 装饰模式

一、基本概念

1.1 定义

装饰模式使用一种对客户端透明的方式来 动态地扩展对象的功能,同时它是继承关系的一种替代方案,其包含以下四种角色:

  • Component:抽象组件。可以是一个接口或抽象类,其充当的就是被装饰的原始对象。
  • ConcreteComponent:组件具体实现类。Component类的基本实现,也是我们装饰的具体对象。
  • Decorator:抽象装饰者。用于装饰组件对象,内部 一定有一个指向组件对象的引用,大多数情况下该类为抽象类,需要根据不同的装饰逻辑实现不同的具体子类。
  • ConcreteDecoratorA:装饰者具体实现类。

1.2 例子

  • 定义抽象组件,其包含抽象方法operate()
/**
 * 抽象组件类。
 *
 * @author lizejun
 **/
public abstract class Component {

    /**
     * 抽象组件类的抽象方法。
     */
    public abstract void operate();
}
  • 定义组件具体实现类。
/**
 * 组件具体实现类。
 *
 * @author lizejun
 **/
public class ConcreteComponent extends Component {

    @Override
    public void operate() {}
}
  • 定义抽象装饰者。
/**
 * 抽象装饰者。
 *
 * @author lizejun
 **/
public class Decorator extends Component {

    private Component mComponent;

    public Decorator(Component component) {
        mComponent = component;
    }

    @Override
    public void operate() {
        mComponent.operate();
    }
}
  • 定义装饰者具体实现类,它包含了两个装饰方法,该方法可以在父类方法的前后调用,为装饰对象 增强功能
/**
 * 装饰者具体实现类。
 * 
 * @author lizejun
 **/
public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operate() {
        //在父类之前调用装饰方法。
        operateBefore();
        super.operate();
        //在父类之后调用装饰方法。
        operateAfter();
    }

    /**
     * 装饰方法 A。
     */
    private void operateBefore() {

    }

    /**
     * 装饰方法 B。
     */
    private void operateAfter() {

    }
}

1.3 应用场景

  • 动态地为一个类新增或删除功能。
  • 不能使用继承,但要提供继承的功能。

1.4 优缺点

优点

  • 可以通过动态地选择不同的 装饰器(被装饰者),来实现不同的功能。
  • 通过 组合替代继承,避免造成的无限扩张。
  • 装饰者和被装饰者 解耦,新增装饰者功能时无需改变被装饰者的代码,符合开闭原则。

缺点

  • 当要修改抽象组件的时候,装饰者的代码也可能需要修改。

二、Android 源码

Android源码当中,有一个装饰模式的典型应用 Context,它的类图我们在之前分析SP的内部实现原理的时候也有分析过, Android 数据存储知识梳理(3) - SharedPreference 源码解析

Context是一个抽象类,在其中定义了我们常用的大量抽象方法,它对应于我们上面谈到的 抽象组件

public abstract class Context {

    public abstract void startActivity(@RequiresPermission Intent intent);

    public abstract void sendBroadcast(@RequiresPermission Intent intent);

    public abstract ComponentName startService(Intent service);

}

其真正的实现是在ContextImpl当中,它继承自Context抽象类,它的角色是 组件的具体实现类

class ContextImpl extends Context {

    @Override
    public void startActivity(Intent intent) {
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    }

    @Override
    public void sendBroadcast(Intent intent) {
        warnIfCallingFromSystemProcess();
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            intent.prepareToLeaveProcess(this);
            ActivityManager.getService().broadcastIntent(
                    mMainThread.getApplicationThread(), intent, resolvedType, null,
                    Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
                    getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @Override
    public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, false, mUser);
    }
}

而上面谈到的装饰者就是最常见的ActivityServiceApplication这三个,它们有一个共同的基类ContextWrapper,而在ContextWrapper中持有一个指向具体抽象对应的引用mBase,当调用Context定义的抽象方法时,其实是由mBase来完成的。

public class ContextWrapper extends Context {

    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }

    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

    @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);
    }

那么mBase是在哪里赋值的呢?

我们以Activity为例,看一下ActivityThread当中的代码,当启动一个Activity的时候,必然需要走到performLaunchActivity方法,这里会创建ContextImplActivity对象,最后通过setOuterContextattach方法建立双向的关联,其中Activity#attach就会给基类中的mBase变量赋值。

  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        //为 Activity 创建 ContextImpl 对象。
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //创建 Activity。
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
       
       //ContextImpl 和 Activity 建立双向的关联关系。
       appContext.setOuterContext(activity);
       activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
  }

三、项目应用

待补充。

四、参考文献

  • <<Android 源码设计模式 - 解析与实战>>

推荐阅读更多精彩内容

  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 972评论 0 9
  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 69,309评论 26 498
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 122,290评论 15 532
  • Android插件化基础的主要内容包括 Android插件化基础1-----加载SD上APKAndroid插件化基...
    隔壁老李头阅读 1,690评论 0 30
  • 你看,展会就是这样,各个乐器品牌万花筒般云集在庞大的国际展馆里。每个展位,都极力把最美的一面呈现出来,无所不用其极...
    在灿烂阳光下阅读 148评论 3 4