安卓设计模式のAdapter模式
1. 模式介紹
模式的定義
適配器模式把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。
使用場景
用電源接口做例子,筆記本電腦的電源一般都是接受5V的電壓,但是我們生活中的電線電壓一般都是220V的輸出。這個時候就出現了不匹配的狀況,在軟件開發中我們稱之為接口不兼容,此時就需要適配器來進行一個接口轉換。在軟件開發中有一句話正好體現了這點:任何問題都可以加一個中間層來解決。這個層我們可以理解為這里的Adapter層,通過這層來進行一個接口轉換就達到了兼容的目的。
2. UML類圖
適配器模式也分兩種,即類適配器模式、對象適配器模式,結構圖如圖1、圖2。
| 圖1 | 圖2 |
如圖所示,類適配器是通過實現Target接口以及繼承Adaptee類來實現接口轉換,而對象適配器模式則是通過實現Target接口和代理Adaptee的某個方法來實現。結構上略有不同。
角色介紹
- 目標(Target)角色:這就是所期待得到的接口。注意:由于這里討論的是類適配器模式,因此目標不可以是類。
- 源(Adapee)角色:現在需要適配的接口。
- 適配器(Adaper)角色:適配器類是本模式的核心。適配器把源接口轉換成目標接口。顯然,這一角色不可以是接口,而必須是具體類。
3. 模式的簡單實現
在上述電源接口這個示例中,5V電壓就是Target接口,220v電壓就是Adaptee類,而將電壓從220V轉換到5V就是Adapter。
類適配器模式
/*** Target角色*/ public interface FiveVolt {public int getVolt5(); }/*** Adaptee角色,需要被轉換的對象*/ public class Volt220 {public int getVolt220() {return 220;} }// adapter角色 public class ClassAdapter extends Volt220 implements FiveVolt {@Overridepublic int getVolt5() {return 5;}}Target角色給出了需要的目標接口,而Adaptee類則是需要被轉換的對象。Adapter則是將Volt220轉換成Target的接口。對應的是Target的目標是要獲取5V的輸出電壓,而Adaptee即正常輸出電壓是220V,此時我們就需要電源適配器類將220V的電壓轉換為5V電壓,解決接口不兼容的問題。
public class Test {public static void main(String[] args) {ClassAdapter adapter = new ClassAdapter();System.out.println("輸出電壓 : " + adapter.getVolt5());} }對象適配器模式
與類的適配器模式一樣,對象的適配器模式把被適配的類的API轉換成為目標類的API,與類的適配器模式不同的是,對象的適配器模式不是使用繼承關系連接到Adaptee類,而是使用代理關系連接到Adaptee類。
從圖2可以看出,Adaptee類 ( Volt220 ) 并沒有getVolt5()方法,而客戶端則期待這個方法。為使客戶端能夠使用Adaptee類,需要提供一個包裝類Adapter。這個包裝類包裝了一個Adaptee的實例,從而此包裝類能夠把Adaptee的API與Target類的API銜接起來。Adapter與Adaptee是委派關系,這決定了適配器模式是對象的。
示例代碼如下 :
/** * Target角色 */ public interface FiveVolt {public int getVolt5(); }/*** Adaptee角色,需要被轉換的對象*/ public class Volt220 {public int getVolt220() {return 220;} }// 對象適配器模式 public class ObjectAdapter implements FiveVolt {Volt220 mVolt220;public ObjectAdapter(Volt220 adaptee) {mVolt220 = adaptee;}public int getVolt220() {return mVolt220.getVolt220();}@Overridepublic int getVolt5() {return 5;}}注意,這里為了節省代碼,我們并沒有遵循一些面向對象的基本原則。
使用示例 :
public class Test {public static void main(String[] args) {ObjectAdapter adapter = new ObjectAdapter(new Volt220());System.out.println("輸出電壓 : " + adapter.getVolt5());} }類適配器和對象適配器的權衡
建議盡量使用對象適配器的實現方式,多用合成/聚合、少用繼承。當然,具體問題具體分析,根據需要來選用實現方式,最適合的才是最好的。
Android ListView中的Adapter模式
在開發過程中,ListView的Adapter是我們最為常見的類型之一。一般的用法大致如下:
// 代碼省略ListView myListView = (ListView)findViewById(listview_id);// 設置適配器myListView.setAdapter(new MyAdapter(context, myDatas));// 適配器 public class MyAdapter extends BaseAdapter{private LayoutInflater mInflater;List<String> mDatas ; public MyAdapter(Context context, List<String> datas){this.mInflater = LayoutInflater.from(context);mDatas = datas ;}@Overridepublic int getCount() {return mDatas.size();}@Overridepublic String getItem(int pos) {return mDatas.get(pos);}@Overridepublic long getItemId(int pos) {return pos;}// 解析、設置、緩存convertView以及相關內容@Overridepublic View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null;// Item View的復用if (convertView == null) {holder = new ViewHolder(); convertView = mInflater.inflate(R.layout.my_listview_item, null);// 獲取titleholder.title = (TextView)convertView.findViewById(R.id.title);convertView.setTag(holder);} else {holder = (ViewHolder)convertView.getTag();}holder.title.setText(mDatas.get(position));return convertView;}}這看起來似乎還挺麻煩的,看到這里我們不禁要問,ListView為什么要使用Adapter模式呢?
我們知道,作為最重要的View,ListView需要能夠顯示各式各樣的視圖,每個人需要的顯示效果各不相同,顯示的數據類型、數量等也千變萬化。那么如何隔離這種變化尤為重要。
Android的做法是增加一個Adapter層來應對變化,將ListView需要的接口抽象到Adapter對象中,這樣只要用戶實現了Adapter的接口,ListView就可以按照用戶設定的顯示效果、數量、數據來顯示特定的Item View。
通過代理數據集來告知ListView數據的個數( getCount函數 )以及每個數據的類型( getItem函數 ),最重要的是要解決Item View的輸出。Item View千變萬化,但終究它都是View類型,Adapter統一將Item View輸出為View ( getView函數 ),這樣就很好的應對了Item View的可變性。
那么ListView是如何通過Adapter模式 ( 不止Adapter模式 )來運作的呢 ?我們一起來看一看。
ListView繼承自AbsListView,Adapter定義在AbsListView中,我們看一看這個類。
AbsListView定義了集合視圖的框架,比如Adapter模式的應用、復用Item View的邏輯、布局Item View的邏輯等。子類只需要覆寫特定的方法即可實現集合視圖的功能,例如ListView。
ListView中的相關方法。
@Override protected void layoutChildren() {// 代碼省略try {super.layoutChildren();invalidate();// 代碼省略// 根據布局模式來布局Item Viewswitch (mLayoutMode) {case LAYOUT_SET_SELECTION:if (newSel != null) {sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);} else {sel = fillFromMiddle(childrenTop, childrenBottom);}break;case LAYOUT_SYNC:sel = fillSpecific(mSyncPosition, mSpecificTop);break;case LAYOUT_FORCE_BOTTOM:sel = fillUp(mItemCount - 1, childrenBottom);adjustViewsUpOrDown();break;case LAYOUT_FORCE_TOP:mFirstPosition = 0;sel = fillFromTop(childrenTop);adjustViewsUpOrDown();break;case LAYOUT_SPECIFIC:sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);break;case LAYOUT_MOVE_SELECTION:sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);break;default:// 代碼省略break;}}// 從上到下填充Item View [ 只是其中一種填充方式 ] private View fillDown(int pos, int nextTop) {View selectedView = null;int end = (mBottom - mTop);if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {end -= mListPadding.bottom;}while (nextTop < end && pos < mItemCount) {// is this the selected item?boolean selected = pos == mSelectedPosition;View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);nextTop = child.getBottom() + mDividerHeight;if (selected) {selectedView = child;}pos++;}return selectedView; }// 添加Item View private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {View child;// 代碼省略 // Make a new view for this position, or convert an unused view if possiblechild = obtainView(position, mIsScrap);// This needs to be positioned and measuredsetupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child; }ListView覆寫了AbsListView中的layoutChilden函數,在該函數中根據布局模式來布局Item View。Item View的個數、樣式都通過Adapter對應的方法來獲取,獲取個數、Item View之后,將這些Item View布局到ListView對應的坐標上,再加上Item View的復用機制,整個ListView就基本運轉起來了。
當然這里的Adapter并不是經典的適配器模式,但是卻是對象適配器模式的優秀示例,也很好的體現了面向對象的一些基本原則。這里的Target角色和Adapter角色融合在一起,Adapter中的方法就是目標方法;而Adaptee角色就是ListView的數據集與Item View,Adapter代理數據集,從而獲取到數據集的個數、元素。
通過增加Adapter一層來將Item View的操作抽象起來,ListView等集合視圖通過Adapter對象獲得Item的個數、數據元素、Item View等,從而達到適配各種數據、各種Item視圖的效果。因為Item View和數據類型千變萬化,Android的架構師們將這些變化的部分交給用戶來處理,通過getCount、getItem、getView等幾個方法抽象出來,也就是將Item View的構造過程交給用戶來處理,靈活地運用了適配器模式,達到了無限適配、擁抱變化的目的。
雜談
優點
更好的復用性
系統需要使用現有的類,而此類的接口不符合系統的需要。那么通過適配器模式就可以讓這些功能得到更好的復用。更好的擴展性
在實現適配器功能的時候,可以調用自己開發的功能,從而自然地擴展系統的功能。
缺點
過多的使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是A接口,其實內部被適配成了B接口的實現,一個系統如果太多出現這種情況,無異于一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。
總結
以上是生活随笔為你收集整理的安卓设计模式のAdapter模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows - 修复所有快捷方式的打
- 下一篇: 【时序】LSTNet:结合 CNN、RN