Android架構設計模式(四)——Adapter Method
一、適配器模式介紹
適配器在平常在生活中是經常會用到的,特別是電子產品。像手機、電腦、家用電器都會用到適配器來轉換電壓的大小,以提供合適的電壓。適配器就是把原來不符合要求的電壓、電路訊號轉換成合適的電壓和訊號。簡單來說,它就是一個介面轉換器。
我們使用適配器的本質原因是:當我們的系統已經確定了一個標準,但已有的資源與現有標準不相容,而且又無法或者不便修改這個標準的時候,就需要用適配器來使得不相容的被使用方封裝成已有的標準供已有的系統使用。
什麼是適配器模式?
定義:
適配器模式把一個類的介面變換成用戶端所期待的另一種介面,從而使得原本因介面不匹配(介面名、返回參數、輸入參數等)而無法一起工作的兩個類能夠在一起工作。
分類:
在軟體程式設計模式中,適配器模式分為兩種:類適配器、對象適配器。
類適配器
概念:通過實現目標Target介面以及繼承Adaptee(需要被適配的類)來實現介面轉換。把Adaptee的介面轉換成Target需要的介面。
UML圖
對象適配器
概念:與類適配器也一樣的目的:把Adaptee的介面轉換成Target需要的介面。但是不同的是,對象適配器是使用代理關係連結到Adaptee類,即將Adaptee作為Adapter中的成員,由Adapter作為代理來實現Adaptee的功能。
UML圖
適配器應用於什麼情境?
1.系統需要使用現有的類,而此類的介面不符合系統的需要,即介面不相容(最普通的適配器);
2.想要建立一個可以重複使用的類,用於將一些彼此之間關聯不大的一些類,包括一些可能在將來引進的類一起工作(Binder介面連結Activity與Service,Activity和Service兩者本就可以獨立運行,通過Binder介面,將Service端的服務適配成Activity端能夠識別的transact()方法)。
3.需要一個統一的輸出介面,而輸入端的類型不可預知(BaseAdapter就是將ListView、GridView等控制項與多變的自訂View結合使用的適配器,輸入端類型為自訂多變的類型,而輸出端得到的總是View類型)
二、Android架構中的適配器模式應用
一般來說,現在都傾向於使用對象適配器,因為對象適配器能夠將被適配對象的方法隱藏,而如果使用類適配器的話,由於繼承的緣故,使得適配器也繼承了被適配對象的方法,這樣會暴露被適配對象。因此,Android中也是使用的對象適配器,通過代理的方法來實現適配。
範例一:ListView+BaseAdapter+自訂View
ListView與BaseAdapter的結合是適配器使用情景的第三種情況。即:需要一個統一的輸出介面,而輸入端的類型不可預知。
自訂View千變萬化,不同View介面又各異,因此通過適配器來提供統一的輸出介面,能夠使得ListView達到以【不變應萬變的效果】。
下面的UML圖,反應了一般的應用中Activity、ListView、BaseAdapter、自訂View之間的聯絡。這裡的觀察者模型只是我自己為了簡化而使得BaseAdapter直接實現Observable(通知者介面),ListView實現Observer介面。實際上BaseAdapter與ListView還有更加深層次的繼承關係,而且觀察者模型是對象觀察者模型(即觀察者和通知者是作為類成員,通過代理實現的),而不是基於介面實現的觀察者模型。
通俗UML圖:
關鍵程式碼分析:
在BaseAdapter中,最重要的方法就是getView()。它是連結ListView容器和其中的ItemView的橋樑,getView(),是一個統一的介面,它固定返回的是View類型的參數,因此無論是哪種類型的自訂視圖,由於它們都是View的子類,因此ListView都能夠識別。這就是BaseAdapter中的適配方法,輸出是不變的,而輸入可以是變化的。
//getView()方法將自訂View進行適配,對各個子View進行裝配,最//後將其裝入一個統一的View之中,返回給ListView。@Overridepublic View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null){ holder = new ViewHolder(); convertView = View.inflate(getBaseContext(),R.layout.activity_audiocable,null); holder.mImageCover = convertView.findViewById(R.id.img_cover); holder.mTextTitle = convertView.findViewById(R.id.txt_title); convertView.setTag(holder); } holder = (ViewHolder) convertView.getTag(); holder.mTextTitle.setText((String) getItem(position).getTitle()); holder.mImageCover.setImageResource((String) getItem(position).getCoverRes()); return convertView; } class ViewHolder{ TextView mTextTitle; ImageView mImageCover; }
範例二:Activity+Binder+MediaPlayer
Activity+Binder+Mediaplayer中,Binder充當適配器。這是適配器使用的第二種情況:想要建立一個可以重複使用的類,用於將一些彼此之間關聯不大的一些類,包括一些可能在將來引進的類一起工作。
我們啟動服務的過程中,Binder就是一個適配器,它提供了transact()方法給架構調用,onTransact()給應用類別實現,而後面的onTransact()方法就是適配方法,通過onTransact方法與不同的後台服務實現對接(多媒體控制、任務下載)。
通俗UML圖:
關鍵程式碼分析:
我們來看一看適配方法onTransact(),順便提一下,如果從架構的角度來看,則onTransact()方法是一個hook方法。onTransact()方法的任務就是負責與多媒體、以及背景工作進行對接。在onTransact()方法中就調用多媒體的控制方法(任務控制方法),從而為前端提供服務。
注意:Activity只知道通過Binder調用transact()方法,其餘的都是通過onTransact()進行適配。
public class mp3Player_Adapter extends Binder{ private MediaPlayer mPlayer = null; private Context ctx; public mp3Player_Adapter(Context cx){ ctx= cx; }//通過onTransact()方法完成調用play()和stop(),對MP3進行播放和//停止的控制。 @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws android.os.RemoteException { reply.writeString(data.readString()+ " mp3"); if(code == 1) this.play(); else if(code == 2) this.stop(); return true; } //play()、stop()都是不能被用戶端識別的方法,需要通過onTransact()來進行適配public void play(){ if(mPlayer != null) return; mPlayer = MediaPlayer.create(ctx, R.raw.test_cbr); try { mPlayer.start(); } catch (Exception e) { Log.e("StartPlay", "error: " + e.getMessage(), e); } } public void stop(){ if (mPlayer != null) { mPlayer.stop(); mPlayer = null; } }}
public class ac01 extends Activity implements OnClickListener { private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT; private final int FP = LinearLayout.LayoutParams.FILL_PARENT; private Button btn, btn2, btn3; public TextView tv; private IBinder ib = null; public void onCreate(Bundle icicle) { //......初始化 startService(in); //啟動服務 //綁定服務 bindService(in, mConnection, Context.BIND_AUTO_CREATE); } //服務串連,用於通知Activity服務串連的情況,同時返回IBinder介面 private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder ibinder) { ib = ibinder; } public void onServiceDisconnected(ComponentName className) {} }; public void onClick(View v) { switch (v.getId()) { case 101: Parcel pc = Parcel.obtain(); Parcel pc_reply = Parcel.obtain(); pc.writeString("playing"); try { //調用適配器方法操作MP3播放 ib.transact(1, pc, pc_reply, 0); tv.setText(pc_reply.readString()); } catch (Exception e) { e.printStackTrace(); } break; case 102: pc = Parcel.obtain(); pc_reply = Parcel.obtain(); pc.writeString("stop"); try { //調用適配器方法操作MP3停止 ib.transact(2, pc, pc_reply, 0); tv.setText(pc_reply.readString()); } catch (Exception e) { e.printStackTrace(); } break; case 103: finish(); break; } }}
三、適配器模式與其他模式的配合
在安卓的組件使用過程中,每一個組件都不止是使用一種設計模式而已,它們都是結合了許多設計模式而形成的。單獨的設計模式是無法設計出實用性、拓展性很好的組件。上面所舉的兩個Android適配器模式的應用也是包含了許多其他設計模式的,只是我們從不同的角度分析而已。下面是我對上面兩個例子進行的分析,因為沒有分析的很深(原始碼的繼承鏈和關係很複雜,只分析表面的幾層),就列出一些模式的組合,當然還有其他的模式,這裡我深究不來,也暫時不願深究。
情景一:適配器+觀察者+模板+策略+組合 = BaseAdapter+ListView+自訂View
整體UML圖
模式分析(不同的視角決定)
適配器模式
從相容的角度來看,那麼BaseAdapter中的四個方法(getItemId()、getItem()、getView()、getCount())都是適配器方法,他們連結了ListView和ListView鑲嵌的那些不確定的多變的自訂View對象。ListView的裝配和初始化只識別這四個方法,適配器將裝入的View對象按照這四個方法分別進行適配,輸出符合ListView要求的介面和參數。
觀察者模式
從資料更新的角度來看,那麼BaseAdapter與ListView之間存在著通知者/觀察者的聯絡。BaseAdapter是一個通知者,而ListView是一個觀察者。BaseAdapter中存放著ListView的資料集合,每當資料集合有更新的時候,就會調用notifyDataChanged()方法,通知ListView進行更新。同時,在ListView更新完畢之後,也可以調用相應的方法反饋給BaseAdapter。
模板模式
從架構變與不變的分離的角度上來看,BaseAdapter、ListView的繼承鏈上都存在著模板模式。即他們都有架構父類,然而架構父類定義了【不變】的部分,然後應用程式(程式員繼承的類別)部分實現【變化】的部分。
策略模式
單從變化的分離來看,那麼BaseAdapter中還存在著策略模式的應用。getView()即是一個策略方法,對於不同的介面getView()會有不同的裝配策略,但是他們的結果和參數都一樣,它承擔著變化的部分。我們實現不同的介面就會繼承BaseAdapter實現不同的getView(),正好就是策略模式的體現。
組合模式
從自訂View本身的布局來看,自訂View是一個由不同布局和控制群組合而形成的,它是組合模式的經典呈現,將系統提供給我們的(或者我們自己實現的一個View)組合成一個新的絢麗的視圖。從這個角度上來看,它便存在著組合模式的應用。
情景二:適配器+觀察者+模板 = Service + Activity + 自訂服務
整體UML圖
模式分析(不同的視角決定)
適配器模式
從適配器模式的角度分析,Binder就是一個適配器類別,它通過一個transact()介面對多媒體(MP3、MP4)、下載任務等不同的對象進行適配,用戶端只需要調用transact(),介面的不相容問題已經由Binder類別進行了適配。
觀察者模式
從Activity與Service通訊的角度來看,Service與Activity還存在著通知者/觀察者關係。Service是通知者,Activity是觀察者。這點可以通過綁定服務體現,我們調用了bindService()後,當服務綁定了之後,Service會調用onBind方法返回一個IBinder介面,然後通過調用ServiceConnection中的onServiceConnect()方法返回一個IBinder介面給Activity用戶端。完成Activity與Service的通訊。
模板模式
從架構中的變與不變分離的角度來看,Service和Activity中各自都存在著模板模式。Service中transact()是模板方法,而onTransact()是hook(卡隼)方法;Activity中callApplicationOnCreate是模板方法,而onCreate()是hook(卡隼)方法。我們程式實現的部分就是卡隼方法,而程式架構部分就是模板方法。
原廠模式
從對象的產生角度來看,Service與Activity中也各自都存在著原廠模式的應用。對於Activity來說,onCreate()就是一個Factory 方法,它建立了系統運行需要的對象;對於Service來說,onCreate()也是一個Factory 方法,它產生了Binder對象,然後再返回給用戶端Activity。
四、總結
適配器模式是一種在系統已經存在且比較大的情況下,突然增加某些模組之間的聯絡,而原有的介面又不符合的情況下才使用的。在Android中,適配器模式的使用是每個Android工程師都再熟悉不過的了,不過在使用適配器的過程中,我們需要明白適配器模式與其他模式混合使用的情況,同時也需要明白適配器存在的原因,什麼時候需要用適配器,什麼時候不需要使用適配器。而且,同樣的一個適配器模式的應用中,可能會存在其他不同的設計模式思想,設計模式之間並不是獨立的,而是相互滲透相互依賴的。