Android设计模式(二十)-装饰模式

装饰模式也叫做包装模式,是结构型设计模式之一。目的是为了给一个类或对象增加行为。可以是继承的一种替代。

装饰模式也很好理解,比如一个人,给他装上火箭就能上天了,装上潜水服就能下海了,但本身还是个人,人没有任何变化。

嘿嘿

定义

动态地给一个对象添加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。

使用场景

  • 需要拓展一个类的功能,增加附加职责时。
  • 需要动态增加功能,并动态删除功能时。
  • 当不能使用继承,但要提供继承的功能时。

UML

  • Component:抽象组件,可以是一个接口或抽象类,是被装饰的原始对象
  • ConcreteComponent:组件的具体实现类。是被装饰的具体对象。
  • Decorator:抽象的装饰者。职责是装饰被装饰的对象。内部一定有一个对被装饰者的引用。一般情况下也是一个抽象类,根据具体逻辑实现不同的子类。如果逻辑简单可以直接是实现类。
  • ConcreteDecoratorA,B:具体的装饰者。

先抽象组件类:

public abstract class Component {
    public abstract void operate();
}

组件的一个具体实现类,也就是被装饰者者:

public class ConcreteComponent extends Component {
    @Override
    public void operate() {
        System.out.println("被装饰者的操作");
    }
}

抽象的装饰者,持有一个被装饰者的引用:

public abstract class Decorator extends Component {
    private Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operate() {
        component.operate();
    }
}

具体的两个装饰者,拓展功能:

public class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operate() {
        operateA();
        super.operate();
        operateB();
    }

    private void operateA(){
        System.out.println("装饰者A在被装饰者的操作之前加些操作");
    }
    private void operateB(){
        System.out.println("装饰者A在被装饰者的操作之前后加些操作");
    }
}
public class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }
    @Override
    public void operate() {
        operateA();
        super.operate();
        operateB();
    }

    private void operateA(){
        System.out.println("装饰者B在被装饰者的操作之前加些操作");
    }
    private void operateB(){
        System.out.println("装饰者B在被装饰者的操作之前后加些操作");
    }
}

客户端调用:

public class Client {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);
        ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);

        concreteDecoratorA.operate();
        concreteDecoratorB.operate();
    }
}

输出:

装饰类并没有在原来的类上做审核改动,只是拓展了一些操作。通过不同的包装类就能拓展不同的功能。而传入不同的被包装类,也能拓展不同的具体对象。

简单实现

拿一开始说的那个人为例子。人是个抽象的概念。男孩是个具体的人。但是这个人要干不同的事情要穿不一样的衣服,就需要进行不同的包装。

抽象的人:

public abstract class Person {
    public abstract void dress();
}

具体的人,也是原始的人,被装饰者:

public class Boy extends Person {
    @Override
    public void dress() {
        System.out.println("穿内衣内裤");
    }
}

抽象的装饰者:

public abstract class PersonDecorator {
    Person person;

    public PersonDecorator(Person person) {
        this.person = person;
    }

    public void dress(){
        person.dress();
    }
}

工作人装饰者:

public class WorkPersonDecorator extends PersonDecorator {
    public WorkPersonDecorator(Person person) {
        super(person);
    }

    @Override
    public void dress() {
        super.dress();
        dressWork();
    }

    private void dressWork(){
        System.out.println("穿西装领带");
    }
}

运动的人装饰者:

public class SportPersonDecorator extends PersonDecorator {
    public SportPersonDecorator(Person person) {
        super(person);
    }

    @Override
    public void dress() {
        super.dress();
        dressSport();
    }
    private void dressSport(){
        System.out.println("穿运动衣");
    }
}

客户端调用:

public class Client {
    public static void main(String[] args) {
        Person boy = new Boy();
        System.out.println("包装一个上班人:");
        WorkPersonDecorator workPersonDecorator = new WorkPersonDecorator(boy);
        workPersonDecorator.dress();
        System.out.println("包装一个运动的人:");
        SportPersonDecorator sportPersonDecorator = new SportPersonDecorator(boy);
        sportPersonDecorator.dress();
    }
}

输出:

Android中的装饰者模式

经常使用的Context其实用的就是包装模式:

先看一下他们的继承关系:

Activity的:

然后是service的:

然后是application的:

他们都继承了ContextWrapper类。根据这个名字就觉得这是一个装饰类。装饰类里面会持有一个被装饰者的引用,找一下:

package android.content;
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 AssetManager getAssets() {
        return mBase.getAssets();
    }
    //.......
}

看到前面这两行就感觉找对了。有两种方法可以给mBase赋值。而且这个类的一切操作都像getAssets方法一样,调用的是mBase的方法。因此可以知道,传进来的mBase才是真正的执行者。

接下来就找一下具体的被包装类是什么。

在分析Activity的onCreate方法调用时,知道activity的创建是在ActivityThread的performLaunchActivity方法中的,再来看一下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        Activity activity = null;
        try {
            //创建Activity
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            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);
            }
        } 
//......
        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            if (activity != null) {
                //创建Activity的Context
                Context appContext = createBaseContextForActivity(r, 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);

                //......
            }
            //......
        } 
        return activity;
    }

createBaseContextForActivity创建的是具体的Activity的Context,查看源码发现是ContextImpl:

private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
        //......这里创建的是ContextImpl。
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.token, displayId, r.overrideConfig);
        appContext.setOuterContext(activity);
        Context baseContext = appContext;
        //......
        return baseContext;
    }

在performLaunchActivity通过createBaseContextForActivity拿到一个ContextImpl之后,会调用activity.attach方法,看这个方法:

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);

        //......
    }

都知道传进来的是前面获取的ContextImpl。

然后调用Activity的父类ContextThemeWrapper的attachBaseContext方法:

@Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
    }

看他有调用了父类的方法,ContextThemeWrapper的父类就是ContextWrapper了,然后就回到了ContextWrapper。

前面可以看到ContextWrapper刚好就有个attachBaseContext,给mBase赋值,因此可以说,至少在Activity上,ContextWrapper这个装饰类里面引用的具体被装饰类是ContextImpl。其实Application和Service的被装饰者也是ContextImpl。

package android.app;
class ContextImpl extends Context {
    //......
    @Override
    public AssetManager getAssets() {
        return getResources().getAssets();
    }
    //......
}

下面就可以画出他们的关系了:

总结

装饰模式和前面的代理模式有点类似,容易把装饰模式看成代理模式。装饰模式是继承的一种替代方案,主要为所装饰的对象增强功能,动态的增加方法。而代理模式主要是为了控制对原有对象的访问权限,不对原有对象进行功能增强。

我觉得两者的区别主要是使用目的的区别。

优点

  • 是继承的一种替代方案,但是比继承要灵活的多,可以在运行时通过传入不同的被装饰器或不同的装饰器来达成不同的行为。
  • 增加新的被装饰类和装饰类很方便,而且不用修改原有代码。便于拓展。符合开闭原则。

缺点

  • 设计模式基本都有这个缺点,就是会生成额外的类,增加系统复杂度。
  • 由于装饰可以层层包装,交叉包装,如果包装的很深的话,调试排错会比较麻烦,也不容易理解。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容