編輯:關於Android編程
這篇來介紹一下適配器模式(Adapter Pattern),適配器模式在開發中使用的頻率也是很高的,像 ListView 和 RecyclerView 的 Adapter 等都是使用的適配器模式。在我們的實際生活中也有很多類似於適配器的例子,比如香港的插座和大陸的插座就是兩種格式的,為了能夠成功適配,一般會在中間加上一個電源適配器,形如:
這樣就能夠將原來不符合的現有系統和目標系統通過適配器成功連接。
說到底,適配器模式是將原來不兼容的兩個類融合在一起,它有點類似於粘合劑,將不同的東西通過一種轉換使得它們能夠協作起來。碰到要在兩個完全沒有關系的類之間進行交互,第一個解決方案是修改各自類的接口,但是如果無法修改源代碼或者其他原因導致無法更改接口,此時怎麼辦?這種情況我們往往會使用一個 Adapter ,在這兩個接口之間創建一個粘合劑接口,將原本無法協作的類進行兼容,而且不用修改原來兩個模塊的代碼,符合開閉原則。
PS:對技術感興趣的同鞋加群544645972一起交流
java/android 設計模式學習筆記目錄
適配器模式把一個類的接口換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。
所以,這個模式可以通過創建適配器進行接口轉換,讓不兼容的接口兼容,這可以讓客戶實現解耦。如果在一段時間之後,我們想要改變接口,適配器可以將改變的部分封裝起來,客戶就不必為了應對不同的接口而每次跟著修改。
適配器模式的使用場景可以有以下幾種:
適配器模式在實際使用過程中有“兩種”方式:對象適配器和類適配器。
首先看一下類適配器模式的 uml 類圖:
類適配器是通過實現 ITarget 接口以及繼承 Adaptee 類來實現接口轉換,目標接口需要的是 operation1() 的操作,而 Adaptee 類只能提供一個 Z喎?/kf/ware/vc/" target="_blank" class="keylink">vcGVyYXRpb24yKCkgtcSy2df3o6zS8rTLvs2z9s/Wwcuyu7zmyN21xMfpv/ajrLTLyrHNqLn9IEFkYXB0ZXIgyrXP1tK7uPYgb3BlcmF0aW9uMSgpILqvyv29qyBBZGFwdGVlILXEIG9wZXJhdGlvbjIoKSDXqru7zqogSVRhcmdldCDQ6NKqtcSy2df3o6zS1LTLyrXP1rzmyN2ho8DgysrF5Mb3xKPKvdPQyP249r3HyaujujwvcD4NClRhcmdldKO6xL+x6r3HyaujrNKyvs3Kx8v5xtq0/bXDtb21xL3Tv9qjrNPJ09rV4sDvzNbC27XEysfA4MrKxeTG98Sjyr2jrNLytMvEv7Hqsru/ydLUysfA4KO7QWRhcHRlZaO6z9bU2tDo0qrKysXktcS907/ao7tBZGFwdGVyo7rKysXkxve9x8mro6zKysXkxvew0dS0vdO/2tequ7uzycS/seq907/ao6zL+dLU1eLSu7j2vcfJq7HY0OvKx77fzOXA4KGjDQo8cD4mbmJzcDs8L3A+DQo8aDIgaWQ9"對象適配器模式">對象適配器模式
對象適配器模式 uml 類圖:
uml 類圖和類適配器模式基本一樣,區別就在於對象適配器模式與 Adaptee 的關系是 Dependency,而類適配器是 Generalization ,一個是依賴,一個是繼承。所以 Adapter 類會持有一個 Adaptee 對象的引用,並且通過 operation1() 方法將該 Adaptee 對象與 ITarget 接口的相關操作銜接起來。
這種實現方式直接將要被適配的對象傳遞到 Adapter 中,使用組合的形式實現接口兼容的效果,這種模式比類適配器模式更加靈活,它的另一個好處是被適配對象中的方法不會暴露出來,而類適配器由於繼承了被適配對象,因此,被適配對象類的函數在 Adapter 類中也都含有,這使得 Adapter 類出現了一些奇怪的接口,用於使用成本較高。因此,對象適配器模式更加靈活和實用。
類適配器模式使用的是繼承的方式,而對象適配器模式則使用的是組合的方法。從設計模式的角度來說,對象適配器模式遵循 OO 設計原則的“多用組合,少用繼承”,這是一個優點,但是類適配器模式有一個好處是它不需要重新實現整個被適配者的行為,畢竟類適配器模式使用的是繼承的方式,當然這麼做的壞處就是失去了使用組合的彈性。
所以在實際過程中需要根據使用情況而定,如果 Adaptee 類的行為很復雜,但是 Adapter 適配器類並不需要這些大部分的無關行為,那麼使用對象適配器模式是合適的,但是如果需要重新實現大部分 Adaptee 的行為,那麼就要考慮是否使用類適配器模式了。
我們以最上面說到的香港的英式三角插座和大陸的三角插座為例,來構造類適配器模式,首先是兩個插座格式類:
IChinaOutlet.class
public interface IChinaOutlet {
public String getChinaType();
}
ChinaOutlet.class
public class ChinaOutlet implements IChinaOutlet{
@Override
public String getChinaType() {
return "Chinese three - pin socket";
}
}
上面是中式插座的輸出格式,然後是香港的英式插座輸出格式:
HKOutlet.class
public class HKOutlet {
public String getHKType() {
return "British three - pin socket";
}
}
為了將香港的英式插座轉換為中式插座,我們需要構造一個 Adapter 類,目的是進行插座格式的轉換:
OutletAdapter.class
public class OutletAdapter extends HKOutlet implements IChinaOutlet{
@Override
public String getChinaType() {
String type = getHKType();
type = type.replace("Chinese", "British");
return type;
}
}
這樣就實現了插座接口的轉換,例子很簡單,明了。當然這個例子很簡單,要的就是要學會這個思想:在不修改原來類的基礎上,將原來類進行擴展後使用在新的目標系統上。
對象適配器模式就以我幾年前寫過的一個 View 作為例子:android一個轉盤效果的容器viewgroup,這個例子就是典型的“需要統一的輸出接口,而輸入端的類型不可預知”情形,需要輸出的是一個個 View ,而輸入的數據是未知的。原先的處理方式是使用動態 addChild 的方式添加子 View,然後使用removeChild 方法刪除子 View :
...
public void addChild(final View view) throws NumberOverFlowException{
if(childNum < maxNum){
//每次添加子view的時候都要重新計算location數組
location.add(new FloatWithFlag());
TurnPlateViewUtil.getLocationByNum(location);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
listener.onClick((String)arg0.getTag());
}
});
view.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View arg0) {
initPopUpWindow();
window.showAsDropDown(arg0);
viewIsBeingLongClick = arg0;
return false;
}
});
addView(view);
childNum++;
}else{
throw new NumberOverFlowException(maxNum);
}
}
public void removeChild(final View view){
try{
this.removeView(view);
location.remove(0);
childNum--;
TurnPlateViewUtil.getLocationByNum(location);
requestLayout();
}catch(Exception e){
}
}
...
使用這種方式會造成外部對子 View 的操縱很繁瑣,換位思考一下,如果 ListView 需要以 addView 和 removeView 的方式去處理,那是極其頭疼的,所以現在我們可以換一種思維進行改進,學習 ListView 的 Adapter 思想,我們也使用適配器的方式進行處理,為了方便這裡就直接繼承 BaseAdapter 吧,改造後的代碼如下:
/**
* 設置適配器
* @param adapter
*/
public void setAdapter(BaseAdapter adapter) throws NumberOverFlowException {
this.adapter = adapter;
if (adapter.getCount() > MAX_NUM) {
throw new NumberOverFlowException(adapter.getCount());
}
adapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
onDataSetChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
onDataSetChanged();
}
});
initChild();
}
/**
* 數據源發生變更,需要重新繪制布局
*/
private void onDataSetChanged(){
initChild();
}
...
private void initChild() {
removeAllViews();
location.clear();
for (int i=0; i < adapter.getCount(); i++) {
//每次添加子view的時候都要重新計算location數組
location.add(new FloatWithFlag());
TurnPlateViewUtil.getLocationByNum(location);
View view = adapter.getView(i, null, this);
view.setTag(i);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
listener.onClick((String)arg0.getTag());
}
});
view.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View arg0) {
initPopUpWindow();
window.showAsDropDown(arg0);
viewIsBeingLongClick = arg0;
return false;
}
});
addView(view);
}
}
外部使用時直接繼承 BaseAdapter 類,然後在對應方法中返回對應 View 即可,這樣就實現了“不同的輸入,同樣的輸出”:
private class TurnPlateViewAdapter extends BaseAdapter{
@Override
public int getCount() {
return 5;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
drawable.setBounds(0, 0,drawable.getMinimumHeight() , drawable.getMinimumHeight());
TextView textview = new TextView(MainActivity.this);
textview.setTextColor(getResources().getColor(android.R.color.white));
textview.setText(R.string.text);
textview.setCompoundDrawables(null, drawable, null, null);
textview.setTag(tag++ +"");
return textview;
}
}
這樣,外部修改輸入數據之後,通知 adapter 數據源變更,因為已經注冊觀察者,所以 TurnplateView 自然而然可以收到通知,並且刷新界面,最後實現效果和以前一樣:
由此感慨,在最初學習 android 的時候,listView 的 adapter 知道怎麼使用,但是並沒有去深究為什麼這麼使用,其實裡面很多地方都透著設計模式的思想,源碼真可謂是第一手學習資料。
Adapter 模式的經典實現在於將原本不兼容的接口融合在一起,使之能夠很好的進行合作。但是,在實際開發中, Adapter 模式也會可以根據實際情況進行適當的變更,最典型的就是 ListView 和 RecyclerView 了,這種設計方式使得整個 UI 架構變得非常靈活,能夠擁抱變化。所以在實際使用的時候,遵循上面說過的三種場景:
系統需要使用現有的類,而此類的接口不符合系統的需要,即接口不兼容;想要建立一個可以重復使用的類,用於與一些彼此之間沒有太大聯系的一些類,包括一些可能在將來引進的類一起工作;需要一個統一的輸出接口,而輸入端的類型不可預知。根據情況進行變化,將適配器模式靈活運用在實際開發中。
https://github.com/zhaozepeng/Design-Patterns/tree/master/AdapterPattern
http://www.android100.org/html/201506/20/155883.html
https://en.wikipedia.org/wiki/Adapter_pattern
主界面的實現 前面已做好了核心布局文件,接下來要做的就是讓客戶端活起來,現在的任務就是實現一個側滑菜單的功能,實現這個功能也並不難,使用V4包下的DrawerLayout
一、概述講解優化查詢相冊圖片之前,我們先來看下PM提出的需求,PM的需求很簡單,就是要做一個類似微信的本地相冊圖片查詢控件,主要包含兩個兩部分: 進入圖片選擇頁面就要顯
如今的人們幾乎每天都會用手機拍攝無數張照片,但是照片一多管理就是一件麻煩事。一般來說,用戶管理照片不是系統的圖庫就是快圖應用,後者雖然浏覽速度快但管理功能卻
Android的媒體效果框架允許開發者可以很容易的應用多種令人印象深刻的視覺效果到照片或視頻之上。作為這個媒體效果的框架,它使用GPU來處理圖片處理的過程,它僅僅接收Op
應用開發中需要獲取WebView當前頁面的標題,可能通過對WebChro