概述
装饰者模式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案
结构说明
- Component抽象组件,是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象。(注:在装饰模式中,必然有一个最基本、最核心、最原始的接口或者抽象类充当Component抽象组件)
- ConcreteComponent具体组件,是最核心、最原始、最基本的接口或抽象类的实现,我们需要装饰的就是它。
- Decorator装饰角色,一般是一个抽象类,实现接口或者抽象方法,它的属性里必然有一个private变量引用指向Component抽象组件。(功能多的话可以独立出个抽象类来,也可以直接ConcreteDecorator)
- ConcreteDecorator具体装饰角色,,我们要把我们最核心的、最原始的、最基本的东西装饰成其它东西。
优点
- 装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的组件。
- 装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component。
- 装饰模式可以动态地扩展一个实现类的功能,通过一种动态的方式来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
缺点
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
举例说明
玩过游戏的兄弟应该都知道,游戏里面每个角色有武器、鞋子、护腕、戒指、还有各种红宝石、蓝宝石、黄宝石等等。
下面需求开始:设计游戏的装备系统,基本要求,要可以计算出每种装备在镶嵌了各种宝石后的攻击力和描述:
开始初步的设想,出于多年面向对象的经验,我们可能会这么设计:
如果你这么设计了,我靠,就这么点需求你写了几百个类,工作量还是蛮大的。
下面用装饰者模式来实现一下
- 首先是装备的超类(Component抽象组件)
|
|
- 然后是武器的类(ConcreteComponent具体组件)
|
|
- 接下来当然是装饰品,宝石了,首先超类(Decorator装饰角色)
|
|
- 可在此超类中增添一些其他的接口方法,以使装饰者的功能更多,当然,装饰者只有一个时也可不写装饰者超类,蓝宝石的装饰品(ConcreteDecorator具体装饰角色)
|
|
- 红宝石的装饰品(ConcreteDecorator具体装饰角色)
|
|
- 接下来就是装饰者装饰被装饰者了
|
|
对上面的装饰者的使用是不是有种似曾相识的感觉呢?
其实在Java的API中也有装饰者模式的身影,一定记得Java里面的各种文件操作的流吧,其实用的便是装饰者的模式
Android中的应用
装饰者模式在android的应用(举个栗子),RecyclerView底部加载更多的的应用就可以用装饰者模式,RecyclerView适配器(RecyclerViewAdapter)是被装饰者,底部加载更多适配器(LoadMoreAdapterWrapper)是装饰者,先来look一look代码块吧(装饰者与被装饰者需继承同一基类)
RecyclerViewAdapter被装饰者代码
此适配器没什么特别,这也是装饰者模式的特点,原有代码无需改变,只是多了个装饰者
装饰者代码
在初始化装饰者类的时候需要传入被装饰者的一个引用,从而进行”装饰”,如上述代码中的
|
|
当然还有一个点击事件的回调,因为RecyclerView没有给我们封装好item的点击事件,这里需要自己在adapter中实现item点击事件的回调,此处就不细讲改点咯~~~
加载更多的布局装饰的步骤主要如下:
|
|
在重写的 getItemViewType(int position) 方法中判断当前界面要获取的显示的item是否是最后一个item(即加载更多或已加载完毕的item),若是,则根据是否还有数据返回相对应的布局id(即加载更多或已加载完毕)。若不是最后一个item,则用刚才传进来的被装饰者的引用(这里为RecyclerView的adapter)调用其getItemViewType(int position)返回各个item的布局id。
这里还有一点要注意的是,getItemCount()的返回值必须要是RecyclerView的adapter中item的的总数加一,如下
|
|
在创建item布局的时候便根据onCreateViewHolder(ViewGroup parent, int viewType)方法中的参数viewType做对应的创建不同的布局,同样,在
onBindViewHolder(RecyclerView.ViewHolder holder, int position)绑定数据时根据holder做不同的处理。
最后,在Activity中进行装饰者和被装饰的绑定,如下
|
|
如此,一个用装饰者模式实现的底部加载更多的RecyclerView就实现了
装饰者模式在Android源码中的应用
在Android源码中,其中一个比较经典的使用到装饰模式的就是由Context(“上帝对象”)抽象类扩展出的ContextWrapper的设计。继承结构如下图所示:
- Context就是我们的抽象组件,它提供了应用运行的基本环境,是各组件和系统服务通信的桥梁,隐藏了应用与系统服务通信的细节,简化了上层应用的开发。所以Contex就是“装饰模式”里的Component。
- Context类是个抽象类,Android.app.ContextImpl派生实现了它的抽象接口。ContextImpl对象会与Android框架层的各个服务(包括组件管理服务、资源管理服务、安装管理服务等)建立远程连接,通过对Android进程间的通信机制(IPC)和这些服务进行通信。所以ContextImpl就是“装饰模式”里的ConcreteComponent。
- 如果上层应用期望改变Context接口的实现,就需要使用android.content.ContextWrapper类,它派生自Context,其中具体实现都是通过组合的方式调用ContextImpl类的实例(在ContextWrapper中的private属性mBase)来完成的。这样的设计,使得ContextImpl与ContextWrapper子类的实现可以单独变化,彼此独立。所以可以看出ContextWrapper就是“装饰模式”里的Decorator。
- Android的界面组件Activity、服务组件Service以及应用基类Application都派生于ContextWrapper,它们可以通过重载来修改Context接口的实现。所以可以看出Activity、服务组件Service以及应用基类Application就是“装饰模式”里的具体装饰角色A、B、C。
Context的源码
|
|
可以看到Context里面提供了很多的抽象方法,包括四大组件的启动,application启动等等。我们看下它的实现类,这里就找startActivity方法,看它的实现
这里至少可以看出的ContextImpl是实现了Context的抽象方法startActivity函数。
现在来看装饰类ContextWrapper如何来调用这个startActivity方法的:
首先必须包含属性Context抽象类的实例对象mBase,看出它只是单纯的调用父类Context的方法mBase.startActivity(intent),并未做修改看看具体装饰类如何来装饰和扩展父类ContextWrapper的(就以service为例子吧,Service和Appication则是直接继承ContextWrapper,而Activity则是继承ContextThemeWrapper,ContextThemeWrapper又继承ContextWrapper,从简单入手好点。。。)
可以看出,装饰者类不断扩展自己的属性和方法,service类增添了不少自身特有的方法,而在Internal API中,在attach()方法中attachBaseContext(context)就是调用的父类ContextWrapper中的方法,可看下ContextWrapper中该方法的实现:
可见,它将Context类型的参数base赋值给了mBase,接着在其他方法中使用该引用调用Context的实现类ContextImpl或其子类的方法,这就是同一基类扩展出来的装饰者内部通过一个被装饰者的引用调用其方法,从而丰富被装饰者功能的装饰者模式了。(同样的Service中getApplicationInfo()等其他方法也是如此)
同时,当我们重写MyService类继承Service后,在MyService中也可重写startActivity(Intent intent)方法,虽然在Service中没有该方法,但调用的其实是父类ContextWrapper中的startActivity(Intent intent),该方法如下:
只是简单地调用了下mBase.startActivity(intent),所以调用的还是Context的具体实现类ContextImpl的startActivity(Intent intent),方法内容如下:
其他的Application和Activity就不一一讲解了。。。。如此看来,装饰者模式在源码中的应用还是很强大的。
附
一个Application对象会有一个Context,而Application(存在于整个应用的生命周期中)在应用中是为唯一的,同时一个Activity或Service有分别表示一个Context,因此,一个应用中Context对象的总数等于Activity对象与Service对象之和加上一个Application,那么四大组件中的其他两个BroadcastReceiver和ContentProvider没有保持Context对象了吗?其实,BroadcastReveicer并非直接或间接继承于Context,但是每次接收广播的时候,onReceiver方法都会收到一个Context对象,该对象时ReceiverREstrictedContext的一个实例;而在ContentProvider中你可以调用getContext方法获取到一个Context对象,这些COntext都直接或间接来自于Application、Activity和Service。