装饰(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