Android设计模式——装饰模式之ContexWrapper源码分析

装饰(Decorator)模式又名包装(Wrapper)模式。在Android中,使用装饰模式一般是以Wrapper结尾,例如ContextWrapper,这也是我们这篇文章要分析的一个类。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案.

装饰模式以客户透明的方式动态地给一个对象附加上更多的责任,也就是客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用更多子类的情况下扩展对象的功能。

装饰模式的关键就是它的这种扩展是完全透明的,说白了它就是对既有的一个类进行了一个包装,并且对这个既有的类的某些功能进行了扩展,用户直接以使用既有的这个类的方式来使用我们的包装类就可以了。

还是直接来个简单的实例吧!
加入我们有一个有一个类Computer如下:

public class Computer {

    public Computer() {}

    public runProgram() {
        //运行程序
        //......
    }
}

这个类很简单,只有一个runProgram方法来运行程序,后期我们如果需要对这个Computer类进行的功能加以扩展,在运行程序之前我们需要首先进行安全检测,我们该怎么办,其实实现的方法有很多,不过使用装饰模式是一种比较好的扩展方式。

    private Computer computer;

    public ComputerWrapper(Computer computer) {
        this.computer = computer;
    }

    public void runProgram() {
        //安全检测业务
        //......
        computer.runProgram();
    }
}

这样我们就对这个成功的进行了扩展,因为我们仅仅是对Computer进行扩展,但需要对客户端进行透明,就是说客户端并不会觉得对象在装饰前和装饰后有什么不同,所以我们通常是也会继承原来的Computer类,方便对它的方法进行复写。

public ComputerWrapper extends Computer {
    private Computer computer;

    public ComputerWrapper(Computer computer) {
        this.computer = computer;
    }

    public void runProgram() {
        //安全检测业务
        //......
        computer.runProgram();
    }
}

上面的这种方式是最简单的方式,为了达到一定的统一性,我们会首先定义一个Computer接口,因为Computer的种类有很多,针对不同的电脑有不同的实现,这样我们可以针对不同的电脑进行扩展。

public interface Computer {

    public void runProgram();

}

如果我们现在有一个MacBook,那么我们只需要实现这个Computer就可以了,然后针对MacBook写入相应的业务。

public class MacBook implements Computer {

    @Override
    public void runProgram() {
        // 写相关的业务代码
    }
}

好了,MacBook好了,现在需求又来了,我们要对这个MacBook在运行runProgram之前加入安全检测业务该怎么办呢?

public ComputerWrapper implements Computer {
    private Computer computer;

    public ComputerWrapper(Computer computer) {
        this.computer = computer;
    }

    public void runProgram() {
        //安全检测业务
        //......
        computer.runProgram();
    }
}

这里可以看到为什么要使用Computer接口了吧,这个包装类中使用了多态,其他的类型的电脑也可以是一个包装类来进行安全检测.

为了使这个包装类也具有一定的多样性,因为针对不同的功能扩展,我们需要可以需要不要的包装类,所以我们可以将这个包装类设计成两层。第一次我们什么不做,仅仅是实现委派功能,在第二层来进行扩展。什么意思呢?看了代码就知道了。

public ComputerWrapper implements Computer {
    private Computer computer;

    public ComputerWrapper(Computer computer) {
        this.computer = computer;
    }

    public void runProgram() {
        computer.runProgram();
    }
}

第一次包装,可以看到这里什么都没做,当执行runProgram时就是将其委派给了内部computer的runProgram,貌似有点得不偿失,执行执行就可以了,干嘛还有包装一层。其实它是为了给他的继承这使用的,因为他的不同继承这可以针对它进行不同的扩展。

加入我们分别要在runProgram之前进行安全检测和系统检测

public ComputerWrapperA extends ComputerWrapper {

    public ComputerWrapperA(Computer computer) {
        super(computer);
    }

    public void runProgram() {
        //安全检测业务
        //......
        super.runProgram();
    }
}
public ComputerWrapperB extends ComputerWrapper {

    public ComputerWrapperB(Computer computer) {
        super(computer);
    }

    public void runProgram() {
        //系统检测业务
        //......
        super.runProgram();
    }
}

好了,基本的思想基本就是这样了,我们把上面的思路画了一张类图如下:
这里写图片描述

其实,
Computer就是抽象构件角色:给出一个抽象接口,以规范准备接收附加责任的对象。
MacBook是具体构件角色:定义一个将要接收附加责任的类。
ComputerWrapper是装饰角色:持有构件对象的实例,并定义一个与抽象构件接口一致的接口。
ComputerWrapperA和ComputerWrapperB是具体装饰角色:负责给构件对象”贴上”附加的责任.

透明性的要求
装饰模式对客户端的透明性要求程序不要声明一个具体构件类型的变量,而应当声明一个抽象构件类型的变量。这样做的好处上面我们也提到过。

半透明的装饰模式
  然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。这就导致了大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。换言之,允许装饰模式改变接口,增加新的方法。

装饰模式的优点
  (1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。

  (2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

装饰模式的缺点
  由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。

上面的装饰模式介绍完了,我们现在就来说说Android里面的一个典型的使用装饰模式的例子吧,那就是ContextWrapper.

我们首先来上一张类图,对比一下上面的哪种类图,基本完全一样,现在头脑中应该只知道Android里面的这些类的关系了吧,下面我们来具体的进行分析分享。

这里写图片描述

我们对比上面的过程,这样更容易理解

Context====PK====Computer

public abstract class Context {
    public abstract AssetManager getAssets();

    public abstract Resources getResources();

    public abstract PackageManager getPackageManager();

    public abstract ContentResolver getContentResolver();

    public abstract Looper getMainLooper();

    public abstract Context getApplicationContext();

   ......
   ......

可以看到Context是一个抽象类,我们上面的Computer是一个接口,这两个差不多,就是Context里面为我们实现了一些方法罢了。

ContextImpl====PK====MacBook

class ContextImpl extends Context {
    private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs;

    /*package*/ LoadedApk mPackageInfo;
    private String mBasePackageName;
    private String mOpPackageName;
    private Resources mResources;
    /*package*/ ActivityThread mMainThread;
    private Context mOuterContext;
    private IBinder mActivityToken = null;
    private ApplicationContentResolver mContentResolver;
    private int mThemeResource = 0;
    private Resources.Theme mTheme = null;
    private PackageManager mPackageManager;
    private Display mDisplay; // may be null if default display
    ......
    ......

ContextImpl就是对Context类的一个实现。跟上面基本是一样的。

ContextWrapper====PK====ComputerWrapper

public class ContextWrapper extends Context {
    Context mBase;

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

    ......
    ......

这个跟上面的思路也是一模一样,这个类只是包装的第一层,它仅仅是实现委派功能。

有些不一样的是Activity是第三次包装,在其中它有进行了ContextThemeWrapper第二层包装。这样灵活运用嘛。

ContextThemeWrapper====PK====ComputerWrapperA

public class ContextThemeWrapper extends ContextWrapper {
    private Context mBase;
    private int mThemeResource;
    private Resources.Theme mTheme;
    private LayoutInflater mInflater;
    private Configuration mOverrideConfiguration;
    private Resources mResources;

    ......
    ......
    @Override public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                mInflater = LayoutInflater.from(mBase).cloneInContext(this);
            }
            return mInflater;
        }
        return mBase.getSystemService(name);
    }

    ......
    ......

这个类典型就是半透明的装饰模式,因为它内部也添加了其他的方法,我们重点来看看getSystemService方法。

@Override public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        if (mInflater == null) {
            mInflater = LayoutInflater.from(mBase).cloneInContext(this);
        }
        return mInflater;
    }
    return mBase.getSystemService(name);
}

这个函数就是对ContextWrapper里面的一个复写,最后一句return mBase.getSystemService(name)其实可以写成super..getSystemService(name)。

这样就成了下面这样:

@Override public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        if (mInflater == null) {
            mInflater = LayoutInflater.from(mBase).cloneInContext(this);
        }
        return mInflater;
    }
    super.getSystemService(name);
}

这样写更能提醒我们说的装饰思想,对比一些我们ComputerWrapperA里面的runProgram().

public void runProgram() {
    //安全检测业务
    //......
    super.runProgram();
}

其他的就不说了,只要把思想掌握了,灵活运用,都差不多。

总结一句就是装饰模式是将一个东西的表皮换掉,但是保持了它的内心。

装饰模式与适配器模式的区别与联系

熟悉适配器模式的人可能会发现,装饰模式和适配器模式都有一个别名,就是包装模式,但是这两个模式是很不一样的。适配器模式的用意是要改变所考虑对象的接口,将其转换成所需要的形式,而装饰模式的用意是保持接口,只是为了扩展功能,一个是转换,一个是扩展。

参考书籍:
1、Java与模式

2、https://github.com/simple-android-framework/android_design_patterns_analysis

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Android源码设计模式解析与实践是一本关于Android系统中的设计模式的书籍,旨在通过解析Android源码中的实际案例来理解和应用设计模式Android系统是一个庞大而复杂的开源项目,其中包含了大量的设计模式。这些设计模式不仅帮助Android系统实现了高效、稳定、易于扩展的特性,也可以为Android开发者提供参考和借鉴的经验。 本书首先介绍了设计模式的概念和基本原理,包括单例模式、工厂模式、观察者模式、策略模式等。然后,结合Android源码中的具体实例,详细讲解了这些设计模式Android系统中的应用场景和实践方法。 例如,书中通过分析Android系统中的Activity、Fragment、View等核心组件的源码,解析了它们是如何应用观察者模式和状态模式来实现界面更新和事件传递的。又如,书中通过分析Android系统中的Handler、Looper、MessageQueue等核心类的源码,讲解了它们是如何应用责任链模式来实现线程间通信和消息处理的。 此外,本书还探讨了Android系统中的一些特殊设计模式,如MVC模式、MVP模式、MVVM模式等,帮助读者理解和应用这些模式来构建更加优雅和可维护的Android应用程序。 总之,通过学习和实践本书中介绍的Android源码设计模式,读者可以更深入地了解Android系统的设计原则和实践经验,提升自己的Android开发技能,并能够更加高效地开发出高质量的Android应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值