设计模式之装饰者模式

概述

装饰者模式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案

结构说明

  • Component抽象组件,是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象。(注:在装饰模式中,必然有一个最基本、最核心、最原始的接口或者抽象类充当Component抽象组件)
  • ConcreteComponent具体组件,是最核心、最原始、最基本的接口或抽象类的实现,我们需要装饰的就是它。
  • Decorator装饰角色,一般是一个抽象类,实现接口或者抽象方法,它的属性里必然有一个private变量引用指向Component抽象组件。(功能多的话可以独立出个抽象类来,也可以直接ConcreteDecorator)
  • ConcreteDecorator具体装饰角色,,我们要把我们最核心的、最原始的、最基本的东西装饰成其它东西。

image

优点

  • 装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的组件。
  • 装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component。
  • 装饰模式可以动态地扩展一个实现类的功能,通过一种动态的方式来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。

缺点

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。

举例说明

玩过游戏的兄弟应该都知道,游戏里面每个角色有武器、鞋子、护腕、戒指、还有各种红宝石、蓝宝石、黄宝石等等。
下面需求开始:设计游戏的装备系统,基本要求,要可以计算出每种装备在镶嵌了各种宝石后的攻击力和描述:
开始初步的设想,出于多年面向对象的经验,我们可能会这么设计:
image
如果你这么设计了,我靠,就这么点需求你写了几百个类,工作量还是蛮大的。
下面用装饰者模式来实现一下

  1. 首先是装备的超类(Component抽象组件)
1
2
3
4
5
6
7
8
9
10
11
/**
* 装备的接口
*/
public interface IEquip
{
//计算攻击力
public int caculateAttack();
//装备的描述
public String description();
}
  1. 然后是武器的类(ConcreteComponent具体组件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 武器
* 攻击力20
*/
**public class ArmEquip implements IEquip
{
@Override
public int caculateAttack()
{
return 20;
}
@Override
public String description()
{
return "屠龙刀";
}
} **
  1. 接下来当然是装饰品,宝石了,首先超类(Decorator装饰角色)
1
2
3
4
5
6
7
/**
* 装饰品的接口
*/
public interface IEquipDecorator extends IEquip
{
}
  1. 可在此超类中增添一些其他的接口方法,以使装饰者的功能更多,当然,装饰者只有一个时也可不写装饰者超类,蓝宝石的装饰品(ConcreteDecorator具体装饰角色)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 蓝宝石装饰品
* 每颗攻击力+5
*/
public class BlueGemDecorator implements IEquipDecorator
{
private IEquip equip;
public BlueGemDecorator(IEquip equip)
{
this.equip = equip;
}
@Override
public int caculateAttack()
{
return 5 + equip.caculateAttack();
}
@Override
public String description()
{
return equip.description() + "+ 蓝宝石";
}
}
  1. 红宝石的装饰品(ConcreteDecorator具体装饰角色)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 红宝石装饰品 每颗攻击力+15
*/
public class RedGemDecorator implements IEquipDecorator
{
private IEquip equip;
public RedGemDecorator(IEquip equip)
{
this.equip = equip;
}
@Override
public int caculateAttack()
{
return 15 + equip.caculateAttack();
}
@Override
public String description()
{
return equip.description() + "+ 红宝石";
}
}
  1. 接下来就是装饰者装饰被装饰者了
1
2
3
4
5
// 一个镶嵌1颗红宝石,1颗蓝宝石的武器
System.out.println(" 一个镶嵌1颗红宝石,1颗蓝宝石的武器");
equip = new RedGemDecorator(new BlueGemDecorator(new ArmEquip()));
System.out.println("攻击力 : " + equip.caculateAttack());
System.out.println("描述 :" + equip.description());

对上面的装饰者的使用是不是有种似曾相识的感觉呢?

其实在Java的API中也有装饰者模式的身影,一定记得Java里面的各种文件操作的流吧,其实用的便是装饰者的模式

image

Android中的应用

装饰者模式在android的应用(举个栗子),RecyclerView底部加载更多的的应用就可以用装饰者模式,RecyclerView适配器(RecyclerViewAdapter)是被装饰者,底部加载更多适配器(LoadMoreAdapterWrapper)是装饰者,先来look一look代码块吧(装饰者与被装饰者需继承同一基类)

RecyclerViewAdapter被装饰者代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* 被装饰者类,适配RecyclerView数据
*/
public class MusicRecyclerAdapter extends BaseAdapter<MusicBean> {
private static final String TAG = "MusicRecyclerAdapter";
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.music_list_item,null);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
view.setLayoutParams(lp);
return new MusicViewHolder(view);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
MusicViewHolder musicViewHolder = (MusicViewHolder)holder;
musicViewHolder.mPosition = position;
MusicBean musicBean = mDataList.get(position);
musicViewHolder.mSongName.setText(musicBean.getSongname());
musicViewHolder.mSinger.setText(musicBean.getSingername());
}
private class MusicViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener{
View mRootView;
TextView mSongName;
TextView mSinger;
int mPosition;
MusicViewHolder(View itemView) {
super(itemView);
mSongName = (TextView)itemView.findViewById(R.id.tv_song_name);
mSinger = (TextView)itemView.findViewById(R.id.tv_singer);
mRootView = itemView.findViewById(R.id.rl_music_item);
mRootView.setOnClickListener(this);
mRootView.setOnLongClickListener(this);
}
@Override
public void onClick(View view) {
if (null != mOnRecyclerViewListener){
mOnRecyclerViewListener.onItemClick(mPosition);
Log.e(TAG, "onClick: "+mPosition);
}
}
@Override
public boolean onLongClick(View view) {
if (null != mOnRecyclerViewListener){
mOnRecyclerViewListener.onItemLongClick(mPosition);
}
return false;
}
}
}

此适配器没什么特别,这也是装饰者模式的特点,原有代码无需改变,只是多了个装饰者

装饰者代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* 装饰者类,用于显示加载更多和已经到底
*/
public class LoadMoreAdapterWrapper extends BaseAdapter<MusicBean> {
private static final String TAG = "LoadMoreAdapterWrapper";
private BaseAdapter mAdapter;
private boolean mHasMoreData = true;
private OnLoadMoreDataRv mMoreDataRecyclerView;
public LoadMoreAdapterWrapper(BaseAdapter baseAdapter, OnLoadMoreDataRv loadMoreDataRecyclerView){
mAdapter = baseAdapter;
mMoreDataRecyclerView = loadMoreDataRecyclerView;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == R.layout.list_item_no_more){
View view = LayoutInflater.from(parent.getContext()).inflate(viewType,parent,false);
return new NoMoreItemVH(view);
}else if (viewType == R.layout.list_item_loading){
View view = LayoutInflater.from(parent.getContext()).inflate(viewType,parent,false);
return new LoadingItemVH(view);
}else {
return mAdapter.onCreateViewHolder(parent,viewType);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof LoadingItemVH){
mMoreDataRecyclerView.loadMoreData();
}else if (holder instanceof NoMoreItemVH){
}else {
mAdapter.onBindViewHolder(holder,position);
}
}
@Override
public int getItemCount() {
return mAdapter.getItemCount() + 1;
}
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1){
if (mHasMoreData){
return R.layout.list_item_loading;
}else {
return R.layout.list_item_no_more;
}
}else {
return mAdapter.getItemViewType(position);
}
}
private static class NoMoreItemVH extends RecyclerView.ViewHolder{
private NoMoreItemVH(View itemView) {
super(itemView);
}
}
private static class LoadingItemVH extends RecyclerView.ViewHolder{
private LoadingItemVH(View itemView) {
super(itemView);
}
}
/**
* 设置是否还有数据
*/
public void setHasMoreData(boolean hasMoreData){
mHasMoreData = hasMoreData;
}
}

在初始化装饰者类的时候需要传入被装饰者的一个引用,从而进行”装饰”,如上述代码中的

1
2
3
4
5
private BaseAdapter mAdapter;
public LoadMoreAdapterWrapper(BaseAdapter baseAdapter, OnLoadMoreDataRv loadMoreDataRecyclerView){
mAdapter = baseAdapter;
mMoreDataRecyclerView = loadMoreDataRecyclerView;
}

当然还有一个点击事件的回调,因为RecyclerView没有给我们封装好item的点击事件,这里需要自己在adapter中实现item点击事件的回调,此处就不细讲改点咯~~~

加载更多的布局装饰的步骤主要如下:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1){
if (mHasMoreData){
return R.layout.list_item_loading;
}else {
return R.layout.list_item_no_more;
}
}else {
return mAdapter.getItemViewType(position);
}
}

在重写的 getItemViewType(int position) 方法中判断当前界面要获取的显示的item是否是最后一个item(即加载更多或已加载完毕的item),若是,则根据是否还有数据返回相对应的布局id(即加载更多或已加载完毕)。若不是最后一个item,则用刚才传进来的被装饰者的引用(这里为RecyclerView的adapter)调用其getItemViewType(int position)返回各个item的布局id。
这里还有一点要注意的是,getItemCount()的返回值必须要是RecyclerView的adapter中item的的总数加一,如下

1
2
3
4
@Override
public int getItemCount() {
return mAdapter.getItemCount() + 1;
}

在创建item布局的时候便根据onCreateViewHolder(ViewGroup parent, int viewType)方法中的参数viewType做对应的创建不同的布局,同样,在
onBindViewHolder(RecyclerView.ViewHolder holder, int position)绑定数据时根据holder做不同的处理。

最后,在Activity中进行装饰者和被装饰的绑定,如下

1
2
3
4
5
//创建被装饰者实例
mRecyclerAdapter = new MusicRecyclerAdapter();
//创建装饰者实例,并传入被装饰者和回调
mLoadMoreAdapterWrapper = new LoadMoreAdapterWrapper(mRecyclerAdapter,this);
mRvMusicList.setAdapter(mLoadMoreAdapterWrapper);

如此,一个用装饰者模式实现的底部加载更多的RecyclerView就实现了

装饰者模式在Android源码中的应用

在Android源码中,其中一个比较经典的使用到装饰模式的就是由Context(“上帝对象”)抽象类扩展出的ContextWrapper的设计。继承结构如下图所示:
image

  1. Context就是我们的抽象组件,它提供了应用运行的基本环境,是各组件和系统服务通信的桥梁,隐藏了应用与系统服务通信的细节,简化了上层应用的开发。所以Contex就是“装饰模式”里的Component。
  2. Context类是个抽象类,Android.app.ContextImpl派生实现了它的抽象接口。ContextImpl对象会与Android框架层的各个服务(包括组件管理服务、资源管理服务、安装管理服务等)建立远程连接,通过对Android进程间的通信机制(IPC)和这些服务进行通信。所以ContextImpl就是“装饰模式”里的ConcreteComponent。
  3. 如果上层应用期望改变Context接口的实现,就需要使用android.content.ContextWrapper类,它派生自Context,其中具体实现都是通过组合的方式调用ContextImpl类的实例(在ContextWrapper中的private属性mBase)来完成的。这样的设计,使得ContextImpl与ContextWrapper子类的实现可以单独变化,彼此独立。所以可以看出ContextWrapper就是“装饰模式”里的Decorator。
  4. Android的界面组件Activity、服务组件Service以及应用基类Application都派生于ContextWrapper,它们可以通过重载来修改Context接口的实现。所以可以看出Activity、服务组件Service以及应用基类Application就是“装饰模式”里的具体装饰角色A、B、C。

Context的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public abstract class Context {
/**
* File creation mode: the default mode, where the created file can only
* be accessed by the calling application (or all applications sharing the
* same user ID).
* @see #MODE_WORLD_READABLE
* @see #MODE_WORLD_WRITEABLE
*/
public static final int MODE_PRIVATE = 0x0000;
/**
* Same as {@link #startActivity(Intent, Bundle)} with no options
* specified.
*
* @param intent The description of the activity to start.
*
* @throws ActivityNotFoundException &nbsp;
*`
* @see #startActivity(Intent, Bundle)
* @see PackageManager#resolveActivity
*/
public abstract void startActivity(Intent intent);
@Nullable
public abstract ComponentName startService(Intent service);
/**
* Disconnect from an application service. You will no longer receive
* calls as the service is restarted, and the service is now allowed to
* stop at any time.
*
* @param conn The connection interface previously supplied to
* bindService(). This parameter must not be null.
*
* @see #bindService
*/
public abstract void unbindService(@NonNull ServiceConnection conn);
...//等等
}

可以看到Context里面提供了很多的抽象方法,包括四大组件的启动,application启动等等。我们看下它的实现类,这里就找startActivity方法,看它的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {
private final static String TAG = "ApplicationContext";
private final static boolean DEBUG = false;
private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
new HashMap<String, SharedPreferencesImpl>();
。。。。。。
@Override
public void startActivity(Intent intent) {
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intent, -1);
}
.....

这里至少可以看出的ContextImpl是实现了Context的抽象方法startActivity函数。
现在来看装饰类ContextWrapper如何来调用这个startActivity方法的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Proxying implementation of Context that simply delegates all of its calls to
* another Context. Can be subclassed to modify behavior without changing
* the original Context.
*/
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
...
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
....
}

首先必须包含属性Context抽象类的实例对象mBase,看出它只是单纯的调用父类Context的方法mBase.startActivity(intent),并未做修改看看具体装饰类如何来装饰和扩展父类ContextWrapper的(就以service为例子吧,Service和Appication则是直接继承ContextWrapper,而Activity则是继承ContextThemeWrapper,ContextThemeWrapper又继承ContextWrapper,从简单入手好点。。。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
.....
private static final String TAG = "Service";
public Service() {
super(null);
}
/** Return the application that owns this service. */
public final Application getApplication() {
return mApplication;
}
/**
* Called by the system when the service is first created. Do not call this method directly.
*/
public void onCreate() {
}
public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
onStart(intent, startId);
return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}
/**
* Called by the system to notify a Service that it is no longer used and is being removed. The
* service should clean up any resources it holds (threads, registered
* receivers, etc) at this point. Upon return, there will be no more calls
* in to this Service object and it is effectively dead. Do not call this method directly.
*/
public void onDestroy() {
}
.....
// ------------------ Internal API ------------------
/**
* @hide
*/
public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {
attachBaseContext(context);
mThread = thread; // NOTE: unused - remove?
mClassName = className;
mToken = token;
mApplication = application;
mActivityManager = (IActivityManager)activityManager;
mStartCompatibility = getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.ECLAIR;
}
.....
}

可以看出,装饰者类不断扩展自己的属性和方法,service类增添了不少自身特有的方法,而在Internal API中,在attach()方法中attachBaseContext(context)就是调用的父类ContextWrapper中的方法,可看下ContextWrapper中该方法的实现:

1
2
3
4
5
6
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}

可见,它将Context类型的参数base赋值给了mBase,接着在其他方法中使用该引用调用Context的实现类ContextImpl或其子类的方法,这就是同一基类扩展出来的装饰者内部通过一个被装饰者的引用调用其方法,从而丰富被装饰者功能的装饰者模式了。(同样的Service中getApplicationInfo()等其他方法也是如此)

同时,当我们重写MyService类继承Service后,在MyService中也可重写startActivity(Intent intent)方法,虽然在Service中没有该方法,但调用的其实是父类ContextWrapper中的startActivity(Intent intent),该方法如下:

1
2
3
4
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}

只是简单地调用了下mBase.startActivity(intent),所以调用的还是Context的具体实现类ContextImpl的startActivity(Intent intent),方法内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
// be launched in.
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
......

其他的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。