天天看點

設計模式之裝飾者模式Decorator

裝飾者模式,動态地将責任附加到對象上。若要擴充功能,裝飾者提供了比繼承更加有彈性的替代方案

結構說明

設計模式之裝飾者模式Decorator

1、Component抽象元件,是一個接口或者是抽象類,就是定義我們最核心的對象,也就是最原始的對象。(注:在裝飾模式中,必然有一個最基本、最核心、最原始的接口或者抽象類充當Component抽象元件)

2、ConcreteComponent具體元件,是最核心、最原始、最基本的接口或抽象類的實作,我們需要裝飾的就是它。

3、Decorator裝飾角色, 一般是一個抽象類,實作接口或者抽象方法,它的屬性裡必然有一個private變量引用指向Component抽象元件。(功能多的話可以獨立出個抽象類來,也可以直接ConcreteDecorator)

4、ConcreteDecorator具體裝飾角色,,我們要把我們最核心的、最原始的、最基本的東西裝飾成其它東西。

優點

1、裝飾類和被裝飾類可以獨立發展,而不會互相耦合。換句話說,Component類無須知道Decorator類,Decorator類是從外部來擴充Component類的功能,而Decorator也不用知道具體的元件。 

2、裝飾模式是繼承關系的一個替代方案。我們看裝飾類Decorator,不管裝飾多少層,傳回的對象還是Component。

3、裝飾模式可以動态地擴充一個實作類的功能, 通過一種動态的方式來擴充一個對象的功能,在運作時選擇不同的裝飾器,進而實作不同的行為, 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合。可以使用多個具體裝飾類來裝飾同一對象,得到功能更為強大的對象。

4、具體構件類與具體裝飾類可以獨立變化,使用者可以根據需要增加新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有代碼無須改變,符合“開閉原則”。

缺點

1、會産生很多的小對象,增加了系統的複雜性

2、這種比繼承更加靈活機動的特性,也同時意味着裝飾模式比繼承更加易于出錯,排錯也很困難,對于多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較為煩瑣

裝飾者的使用場景

1、在不影響其他對象的情況下,以動态、透明的方式給單個對象添加職責。

2、需要動态地給一個對象增加功能,這些功能也可以動态地被撤銷。  當不能采用繼承的方式對系統進行擴充或者采用繼承不利于系統擴充和維護時。

例子說明

玩過遊戲的兄弟應該都知道,遊戲裡面每個角色有武器、鞋子、護腕、戒指、還有各種紅寶石、藍寶石、黃寶石等等。

下面需求開始:設計遊戲的裝備系統,基本要求,要可以計算出每種裝備在鑲嵌了各種寶石後的攻擊力和描述:

開始初步的設想,出于多年面向對象的經驗,我們可能會這麼設計:

設計模式之裝飾者模式Decorator

如果你這麼設計了,我靠,就這麼點需求你寫了幾百個類,工作量還是蠻大的。

下面用裝飾者模式來實作一下 首先是裝備的超類(Component抽象元件)

/**               * 裝備的接口               */                public interface IEquip                {                //計算攻擊力               public int caculateAttack();                    //裝備的描述               public String description();                }             

然後是武器的類(ConcreteComponent具體元件)

/**               * 武器               * 攻擊力20               */                public class ArmEquip implements IEquip                {                    @Override                public int caculateAttack()                {                return 20;                }                    @Override                public String description()                {                return "屠龍刀";                }                }             

接下來當然是裝飾品,寶石了,首先超類(Decorator裝飾角色)

/**               * 裝飾品的接口               */                public interface IEquipDecorator extends IEquip                {                    }           

可在此超類中增添一些其他的接口方法,以使裝飾者的功能更多,當然,裝飾者隻有一個時也可不寫裝飾者超類 藍寶石的裝飾品 (ConcreteDecorator具體裝飾角色)

/**               * 藍寶石裝飾品               * 每顆攻擊力+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() + "+ 藍寶石";                }                    }             

紅寶石的裝飾品(ConcreteDecorator具體裝飾角色)

/**               * 紅寶石裝飾品 每顆攻擊力+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顆藍寶石的武器                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裡面的各種檔案操作的流吧,其實用的便是裝飾者的模式

設計模式之裝飾者模式Decorator

Android中的應用 裝飾者模式在android的應用(舉個栗子),RecyclerView底部加載更多的的應用就可以用裝飾者模式,RecyclerView擴充卡(RecyclerViewAdapter)是被裝飾者,底部加載更多擴充卡 (LoadMoreAdapterWrapper) 是裝飾者,先來look一look代碼塊吧(裝飾者與被裝飾者需繼承同一基類) RecyclerViewAdapter被裝飾者代碼

/**              *  被裝飾者類,适配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;              }              }              }           

此擴充卡沒什麼特别,這也是裝飾者模式的特點,原有代碼無需改變,隻是多了個裝飾者 裝飾者代碼

/**              * 裝飾者類,用于顯示加載更多和已經到底              */              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;              }                  }           

在初始化裝飾者類的時候需要傳入被裝飾者的一個引用,進而進行"裝飾",如上述代碼中的

private BaseAdapter mAdapter;                  public LoadMoreAdapterWrapper(BaseAdapter baseAdapter, OnLoadMoreDataRv loadMoreDataRecyclerView){              mAdapter = baseAdapter;              mMoreDataRecyclerView = loadMoreDataRecyclerView;              }           

當然還有一個點選事件的回調,因為RecyclerView沒有給我們封裝好item的點選事件,這裡需要自己在adapter中實作item點選事件的回調,此處就不細講改點咯~~~ 加載更多的布局裝飾的步驟主要如下:

@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的的總數加一,如下

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

在建立item布局的時候便根據onCreateViewHolder(ViewGroup parent, int viewType)方法中的參數viewType做對應的建立不同的布局,同樣,在 onBindViewHolder(RecyclerView.ViewHolder holder, int position)綁定資料時根據holder做不同的處理。

最後,在Activity中進行裝飾者和被裝飾的綁定,如下

//建立被裝飾者執行個體              mRecyclerAdapter = new MusicRecyclerAdapter();              //建立裝飾者執行個體,并傳入被裝飾者和回調              mLoadMoreAdapterWrapper = new LoadMoreAdapterWrapper(mRecyclerAdapter,this);              mRvMusicList.setAdapter(mLoadMoreAdapterWrapper);           

如此,一個用裝飾者模式實作的底部加載更多的RecyclerView就實作了

裝飾者模式在Android源碼中的應用 在Android源碼中,其中一個比較經典的使用到裝飾模式的就是由Context("上帝對象")抽象類擴充出的ContextWrapper的設計。繼承結構如下圖所示:

設計模式之裝飾者模式Decorator

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的源碼的部分

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方法 ,看它的實作

/**              * 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方法的:

/**              * 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,從簡單入手好點。。。)

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中該方法的實作:

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),該方法如下:

@Override              public void startActivity(Intent intent) {              mBase.startActivity(intent);              }           

隻是簡單地調用了下mBase.startActivity(intent),是以調用的還是Context的具體實作類ContextImpl的startActivity(Intent intent),方法内容如下:

@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。

參考博文連結: http://www.cnblogs.com/yemeishu/archive/2012/12/30/2839489.html http://blog.csdn.net/lmj623565791/article/details/24269409