Android開發中常見的設計模式
對於開發人員來說,設計模式有時候就是一道坎,但是設計模式又非常有用,過了這道坎,它可以讓你水平提高一個檔次。而在android開發中,必要的瞭解一些設計模式又是非常有必要的。對於想系統的學習設計模式的同學,這裡推薦2本書。一本是Head First系列的Head Hirst Design Pattern,英文好的可以看英文,可以多讀幾遍。另外一本是大話設計模式。
單例模式
首先了解一些單例模式的概念。
確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項。
這樣做有以下幾個優點
- 對於那些比較耗記憶體的類,只例項化一次可以大大提高效能,尤其是在移動開發中。
- 保持程式執行的時候該中始終只有一個例項存在記憶體中
其實單例有很多種實現方式,但是個人比較傾向於其中1種。可以見單例模式
程式碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Singleton { private static volatile Singleton instance = null; private Singleton(){ } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } |
要保證單例,需要做一下幾步
- 必須防止外部可以呼叫建構函式進行例項化,因此建構函式必須私有化。
- 必須定義一個靜態函式獲得該單例
- 單例使用volatile修飾
- 使用synchronized 進行同步處理,並且雙重判斷是否為null,我們看到synchronized (Singleton.class)裡面又進行了是否為null的判斷,這是因為一個執行緒進入了該程式碼,如果另一個執行緒在等待,這時候前一個執行緒建立了一個例項出來完畢後,另一個執行緒獲得鎖進入該同步程式碼,例項已經存在,沒必要再次建立,因此這個判斷是否是null還是必須的。
至於單例的併發測試,可以使用CountDownLatch,使用await()等待鎖釋放,使用countDown()釋放鎖從而達到併發的效果。可以見下面的程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(1); int threadCount = 1000; for (int i = 0; i < threadCount; i++) { new Thread() { @Override public void run() { try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Singleton.getInstance().hashCode()); } }.start(); } latch.countDown(); } |
看看打印出來的hashCode會不會出現不一樣即可,理論上是全部都一樣的。
而在Android中,很多地方用到了單例。
比如Android-Universal-Image-Loader中的單例
1 2 3 4 5 6 7 8 9 10 11 12 |
private volatile static ImageLoader instance; /** Returns singleton class instance */ public static ImageLoader getInstance() { if (instance == null) { synchronized (ImageLoader.class) { if (instance == null) { instance = new ImageLoader(); } } } return instance; } |
比如EventBus中的單例
1 2 3 4 5 6 7 8 9 10 11 |
private static volatile EventBus defaultInstance; public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; } |
上面的單例都是比較規規矩矩的,當然實際上有很多單例都是變了一個樣子,單本質還是單例。
如InputMethodManager 中的單例
1 2 3 4 5 6 7 8 9 10 11 |
static InputMethodManager sInstance; public static InputMethodManager getInstance() { synchronized (InputMethodManager.class) { if (sInstance == null) { IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); IInputMethodManager service = IInputMethodManager.Stub.asInterface(b); sInstance = new InputMethodManager(service, Looper.getMainLooper()); } return sInstance; } } |
AccessibilityManager 中的單例,看程式碼這麼長,其實就是進行了一些判斷,還是一個單例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
private static AccessibilityManager sInstance; public static AccessibilityManager getInstance(Context context) { synchronized (sInstanceSync) { if (sInstance == null) { final int userId; if (Binder.getCallingUid() == Process.SYSTEM_UID || context.checkCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS) == PackageManager.PERMISSION_GRANTED || context.checkCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL) == PackageManager.PERMISSION_GRANTED) { userId = UserHandle.USER_CURRENT; } else { userId = UserHandle.myUserId(); } IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); sInstance = new AccessibilityManager(context, service, userId); } } return sInstance; } |
當然單例還有很多種寫法,比如惡漢式,有興趣的自己去了解就好了。
最後,我們應用一下單例模式。典型的一個應用就是管理我們的Activity,下面這個可以作為一個工具類,程式碼也很簡單,也不做什麼解釋了。
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 |
public class ActivityManager { private static volatile ActivityManager instance; private Stack<Activity> mActivityStack = new Stack<Activity>(); private ActivityManager(){ } public static ActivityManager getInstance(){ if (instance == null) { synchronized (ActivityManager.class) { if (instance == null) { instance = new ActivityManager(); } } return instance; } public void addActicity(Activity act){ mActivityStack.push(act); } public void removeActivity(Activity act){ mActivityStack.remove(act); } public void killMyProcess(){ int nCount = mActivityStack.size(); for (int i = nCount - 1; i >= 0; i--) { Activity activity = mActivityStack.get(i); activity.finish(); } mActivityStack.clear(); android.os.Process.killProcess(android.os.Process.myPid()); } } |
這個類可以在開源中國的幾個客戶端中找到類似的原始碼
以上兩個類是一樣的,沒區別。
Build模式
瞭解了單例模式,接下來介紹另一個常見的模式——Builder模式。
那麼什麼是Builder模式呢。你通過搜尋,會發現大部分網上的定義都是
將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示
但是看完這個定義,並沒有什麼卵用,你依然不知道什麼是Builder設計模式。在此個人的態度是學習設計模式這種東西,不要過度在意其定義,定義往往是比較抽象的,學習它最好的例子就是通過樣例程式碼。
我們通過一個例子來引出Builder模式。假設有一個Person類,我們通過該Person類來構建一大批人,這個Person類裡有很多屬性,最常見的比如name,age,weight,height等等,並且我們允許這些值不被設定,也就是允許為null,該類的定義如下。
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 |
public class Person { private String name; private int age; private double height; private double weight; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } } |
然後我們為了方便可能會定義一個構造方法。
1 2 3 4 5 6 |
public Person(String name, int age, double height, double weight) { this.name = name; this.age = age; this.height = height; this.weight = weight; } |
或許為了方便new物件,你還會定義一個空的構造方法
1 2 |
public Person() { } |
甚至有時候你很懶,只想傳部分引數,你還會定義如下類似的構造方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public Person(String name) { this.name = name; } public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, int age, double height) { this.name = name; this.age = age; this.height = height; } |
於是你就可以這樣建立各個需要的物件
1 2 3 4 5 |
Person p1=new Person(); Person p2=new Person("張三"); Person p3=new Person("李四",18); Person p4=new Person("王五",21,180); Person p5=new Person("趙六",17,170,65.4); |
可以想象一下這樣建立的壞處,最直觀的就是四個引數的建構函式的最後面的兩個引數到底是什麼意思,可讀性不怎麼好,如果不點選看原始碼,鬼知道哪個是weight哪個是height。還有一個問題就是當有很多引數時,編寫這個建構函式就會顯得異常麻煩,這時候如果換一個角度,試試Builder模式,你會發現程式碼的可讀性一下子就上去了。
我們給Person增加一個靜態內部類Builder類,並修改Person類的建構函式,程式碼如下。
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 67 68 69 70 71 72 |
public class Person { private String name; private int age; private double height; private double weight; privatePerson(Builder builder) { this.name=builder.name; this.age=builder.age; this.height=builder.height; this.weight=builder.weight; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } static class Builder{ private String name; private int age; private double height; private double weight; public Builder name(String name){ this.name=name; return this; } public Builder age(int age){ this.age=age; return this; } public Builder height(double height){ this.height=height; return this; } public Builder weight(double weight){ this.weight=weight; return this; } public Person build(){ return new Person(this); } } } |
從上面的程式碼中我們可以看到,我們在Builder類裡定義了一份與Person類一模一樣的變數,通過一系列的成員函式進行設定屬性值,但是返回值都是this,也就是都是Builder物件,最後提供了一個build函式用於建立Person物件,返回的是Person物件,對應的建構函式在Person類中進行定義,也就是建構函式的入參是Builder物件,然後依次對自己的成員變數進行賦值,對應的值都是Builder物件中的值。此外Builder類中的成員函式返回Builder物件自身的另一個作用就是讓它支援鏈式呼叫,使程式碼可讀性大大增強。
於是我們就可以這樣建立Person類。
1 2 3 4 5 6 7 |
Person.Builder builder=new Person.Builder(); Person person=builder .name("張三") .age(18) .height(178.5) .weight(67.4) .build(); |
有沒有覺得建立過程一下子就變得那麼清晰了。對應的值是什麼屬性一目瞭然,可讀性大大增強。
其實在Android中, Builder模式也是被大量的運用。比如常見的對話方塊的建立
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
AlertDialog.Builder builder=new AlertDialog.Builder(this); AlertDialog dialog=builder.setTitle("標題") .setIcon(android.R.drawable.ic_dialog_alert) .setView(R.layout.myview) .setPositiveButton(R.string.positive, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .setNegativeButton(R.string.negative, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .create(); dialog.show(); |
其實在java中有兩個常見的類也是Builder模式,那就是StringBuilder和StringBuffer,只不過其實現過程簡化了一點罷了。
我們再找找Builder模式在各個框架中的應用。
如Gson中的GsonBuilder,程式碼太長了,就不貼了,有興趣自己去看原始碼,這裡只貼出其Builder的使用方法。
1 2 3 4 5 6 |
GsonBuilder builder=new GsonBuilder(); Gson gson=builder.setPrettyPrinting() .disableHtmlEscaping() .generateNonExecutableJson() .serializeNulls() .create(); |
還有EventBus中也有一個Builder,只不過這個Builder外部訪問不到而已,因為它的建構函式不是public的,但是你可以在EventBus這個類中看到他的應用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public static EventBusBuilder builder() { return new EventBusBuilder(); } public EventBus() { this(DEFAULT_BUILDER); } EventBus(EventBusBuilder builder) { subscriptionsByEventType = new HashMap<Class<?>, CopyOnWriteArrayList<Subscription>>(); typesBySubscriber = new HashMap<Object, List<Class<?>>>(); stickyEvents = new ConcurrentHashMap<Class<?>, Object>(); mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10); backgroundPoster = new BackgroundPoster(this); asyncPoster = new AsyncPoster(this); subscriberMethodFinder = new SubscriberMethodFinder(builder.skipMethodVerificationForClasses); logSubscriberExceptions = builder.logSubscriberExceptions; logNoSubscriberMessages = builder.logNoSubscriberMessages; sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent; sendNoSubscriberEvent = builder.sendNoSubscriberEvent; throwSubscriberException = builder.throwSubscriberException; eventInheritance = builder.eventInheritance; executorService = builder.executorService; } |
再看看著名的網路請求框架OkHttp
1 2 3 4 5 |
Request.Builder builder=new Request.Builder(); Request request=builder.addHeader("","") .url("") .post(body) .build(); |
除了Request外,Response也是通過Builder模式建立的。貼一下Response的建構函式
1 2 3 4 5 6 7 8 9 10 11 12 |
private Response(Builder builder) { this.request = builder.request; this.protocol = builder.protocol; this.code = builder.code; this.message = builder.message; this.handshake = builder.handshake; this.headers = builder.headers.build(); this.body = builder.body; this.networkResponse = builder.networkResponse; this.cacheResponse = builder.cacheResponse; this.priorResponse = builder.priorResponse; } |
可見各大框架中大量的運用了Builder模式。最後總結一下
- 定義一個靜態內部類Builder,內部的成員變數和外部類一樣
- Builder類通過一系列的方法用於成員變數的賦值,並返回當前物件本身(this)
- Builder類提供一個build方法或者create方法用於建立對應的外部類,該方法內部呼叫了外部類的一個私有建構函式,該建構函式的引數就是內部類Builder
- 外部類提供一個私有建構函式供內部類呼叫,在該建構函式中完成成員變數的賦值,取值為Builder物件中對應的值
觀察者模式
前面介紹了單例模式和Builder模式,這部分著重介紹一下觀察者模式。先看下這個模式的定義。
定義物件間的一種一對多的依賴關係,當一個物件的狀態傳送改變時,所有依賴於它的物件都能得到通知並被自動更新
還是那句話,定義往往是抽象的,要深刻的理解定義,你需要自己動手實踐一下。
先來講幾個情景。
-
情景1
有一種簡訊服務,比如天氣預報服務,一旦你訂閱該服務,你只需按月付費,付完費後,每天一旦有天氣資訊更新,它就會及時向你傳送最新的天氣資訊。
-
情景2
雜誌的訂閱,你只需向郵局訂閱雜誌,繳納一定的費用,當有新的雜誌時,郵局會自動將雜誌送至你預留的地址。
觀察上面兩個情景,有一個共同點,就是我們無需每時每刻關注我們感興趣的東西,我們只需做的就是訂閱感興趣的事物,比如天氣預報服務,雜誌等,一旦我們訂閱的事物發生變化,比如有新的天氣預報資訊,新的雜誌等,被訂閱的事物就會即時通知到訂閱者,即我們。而這些被訂閱的事物可以擁有多個訂閱者,也就是一對多的關係。當然,嚴格意義上講,這個一對多可以包含一對一,因為一對一是一對多的特例,沒有特殊說明,本文的一對多包含了一對一。
現在你反過頭來看看觀察者模式的定義,你是不是豁然開朗了。
然後我們看一下觀察者模式的幾個重要組成。
- 觀察者,我們稱它為Observer,有時候我們也稱它為訂閱者,即Subscriber
- 被觀察者,我們稱它為Observable,即可以被觀察的東西,有時候還會稱之為主題,即Subject
至於觀察者模式的具體實現,這裡帶帶大家實現一下場景一,其實java中提供了Observable類和Observer介面供我們快速的實現該模式,但是為了加深印象,我們不使用這兩個類。
場景1中我們感興趣的事情是天氣預報,於是,我們應該定義一個Weather實體類。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Weather { private String description; public Weather(String description) { this.description = description; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } @Override public String toString() { return "Weather{" + "description='" + description + '\'' + '}'; } } |
然後定義我們的被觀察者,我們想要這個被觀察者能夠通用,將其定義成泛型。內部應該暴露register和unregister方法供觀察者訂閱和取消訂閱,至於觀察者的儲存,直接用ArrayList即可,此外,當有主題內容傳送改變時,會即時通知觀察者做出反應,因此應該暴露一個notifyObservers方法,以上方法的具體實現見如下程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Observable<T> { List<Observer<T>> mObservers = new ArrayList<Observer<T>>(); public void register(Observer<T> observer) { if (observer == null) { throw new NullPointerException("observer == null"); } synchronized (this) { if (!mObservers.contains(observer)) mObservers.add(observer); } } public synchronized void unregister(Observer<T> observer) { mObservers.remove(observer); } public void notifyObservers(T data) { for (Observer<T> observer : mObservers) { observer.onUpdate(this, data); } } } |
而我們的觀察者,只需要實現一個觀察者的介面Observer,該介面也是泛型的。其定義如下。
1 2 3 |
public interface Observer<T> { void onUpdate(Observable<T> observable,T data); } |
一旦訂閱的主題傳送變換就會回撥該介面。
我們來使用一下,我們定義了一個天氣變換的主題,也就是被觀察者,還有兩個觀察者觀察天氣變換,一旦變換了,就打印出天氣資訊,注意一定要呼叫被觀察者的register進行註冊,否則會收不到變換資訊。而一旦不敢興趣了,直接呼叫unregister方法進行取消註冊即可
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 |
public class Main { public static void main(String [] args){ Observable<Weather> observable=new Observable<Weather>(); Observer<Weather> observer1=new Observer<Weather>() { @Override public void onUpdate(Observable<Weather> observable, Weather data) { System.out.println("觀察者1:"+data.toString()); } }; Observer<Weather> observer2=new Observer<Weather>() { @Override public void onUpdate(Observable<Weather> observable, Weather data) { System.out.println("觀察者2:"+data.toString()); } }; observable.register(observer1); observable.register(observer2); Weather weather=new Weather("晴轉多雲"); observable.notifyObservers(weather); Weather weather1=new Weather("多雲轉陰"); observable.notifyObservers(weather1); observable.unregister(observer1); Weather weather2=new Weather("颱風"); observable.notifyObservers(weather2); } } |
最後的輸出結果也是沒有什麼問題的,如下
觀察者1:Weather{description=’晴轉多雲’}
觀察者2:Weather{description=’晴轉多雲’}
觀察者1:Weather{description=’多雲轉陰’}
觀察者2:Weather{description=’多雲轉陰’}
觀察者2:Weather{description=’颱風’}
接下來我們看看觀察者模式在android中的應用。我們從最簡單的開始。還記得我們為一個Button設定點選事件的程式碼嗎。
1 2 3 4 5 6 7 |
Button btn=new Button(this); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e("TAG","click"); } }); |
其實嚴格意義上講,這個最多算是回撥,但是我們可以將其看成是一對一的觀察者模式,即只有一個觀察者。
其實只要是set系列的設定監聽器的方法最多都只能算回撥,但是有一些監聽器式add進去的,這種就是觀察者模式了,比如RecyclerView中的addOnScrollListener方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
private List<OnScrollListener> mScrollListeners; public void addOnScrollListener(OnScrollListener listener) { if (mScrollListeners == null) { mScrollListeners = new ArrayList<OnScrollListener>(); } mScrollListeners.add(listener); } public void removeOnScrollListener(OnScrollListener listener) { if (mScrollListeners != null) { mScrollListeners.remove(listener); } } public void clearOnScrollListeners() { if (mScrollListeners != null) { mScrollListeners.clear(); } } |
然後有滾動事件時便會觸發觀察者進行方法回撥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public abstract static class OnScrollListener { public void onScrollStateChanged(RecyclerView recyclerView, int newState){} public void onScrolled(RecyclerView recyclerView, int dx, int dy){} } void dispatchOnScrolled(int hresult, int vresult) { //... if (mScrollListeners != null) { for (int i = mScrollListeners.size() - 1; i >= 0; i--) { mScrollListeners.get(i).onScrolled(this, hresult, vresult); } } } void dispatchOnScrollStateChanged(int state) { //... if (mScrollListeners != null) { for (int i = mScrollListeners.size() - 1; i >= 0; i--) { mScrollListeners.get(i).onScrollStateChanged(this, state); } } } |
類似的方法很多很多,都是add監聽器系列的方法,這裡也不再舉例。
還有一個地方就是Android的廣播機制,其本質也是觀察者模式,這裡為了簡單方便,直接拿本地廣播的程式碼說明,即LocalBroadcastManager。
我們平時使用本地廣播主要就是下面四個方法
1 2 3 4 |
LocalBroadcastManager localBroadcastManager=LocalBroadcastManager.getInstance(this); localBroadcastManager.registerReceiver(BroadcastReceiver receiver, IntentFilter filter); localBroadcastManager.unregisterReceiver(BroadcastReceiver receiver); localBroadcastManager.sendBroadcast(Intent intent) |
呼叫registerReceiver方法註冊廣播,呼叫unregisterReceiver方法取消註冊,之後直接使用sendBroadcast傳送廣播,傳送廣播之後,註冊的廣播會收到對應的廣播資訊,這就是典型的觀察者模式。具體的原始碼這裡也不貼。
android系統中的觀察者模式還有很多很多,有興趣的自己去挖掘,接下來我們看一下一些開源框架中的觀察者模式。一說到開源框架,你首先想到的應該是EventBus。沒錯,EventBus也是基於觀察者模式的。
觀察者模式的三個典型方法它都具有,即註冊,取消註冊,傳送事件
1 2 3 4 |
EventBus.getDefault().register(Object subscriber); EventBus.getDefault().unregister(Object subscriber); EventBus.getDefault().post(Object event); |
內部原始碼也不展開了。接下來看一下重量級的庫,它就是RxJava,由於學習曲線的陡峭,這個庫讓很多人望而止步。
建立一個被觀察者
1 2 3 4 5 6 7 8 9 |
Observable<String> myObservable = Observable.create( new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> sub) { sub.onNext("Hello, world!"); sub.onCompleted(); } } ); |
建立一個觀察者,也就是訂閱者
1 2 3 4 5 6 7 8 9 10 |
Subscriber<String> mySubscriber = new Subscriber<String>() { @Override public void onNext(String s) { System.out.println(s); } @Override public void onCompleted() { } @Override public void onError(Throwable e) { } }; |
觀察者進行事件的訂閱
1 |
myObservable.subscribe(mySubscriber); |
具體原始碼也不展開,不過RxJava這個開源庫的原始碼個人還是建議很值得去看一看的。
總之,在Android中觀察者模式還是被用得很頻繁的。
原型模式
這部分介紹的模式其實很簡單,即原型模式,按照慣例,先看定義。
用原型例項指定建立物件的種類,並通過拷貝這些原型建立新的物件。
這是什麼鬼哦,本寶寶看不懂!不必過度在意這些定義,自己心裡明白就ok了。沒程式碼你說個jb。
首先我們定義一個Person類
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 |
public class Person{ private String name; private int age; private double height; private double weight; public Person(){ } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", height=" + height + ", weight=" + weight + '}'; } } |
要實現原型模式,只需要按照下面的幾個步驟去實現即可。
- 實現Cloneable介面
1 2 3 |
public class Person implements Cloneable{ } |
- 重寫Object的clone方法
1 2 3 4 |
@Override public Object clone(){ return null; } |
- 實現clone方法中的拷貝邏輯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Override public Object clone(){ Person person=null; try { person=(Person)super.clone(); person.name=this.name; person.weight=this.weight; person.height=this.height; person.age=this.age; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return person; } |
測試一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Main { public static void main(String [] args){ Person p=new Person(); p.setAge(18); p.setName("張三"); p.setHeight(178); p.setWeight(65); System.out.println(p); Person p1= (Person) p.clone(); System.out.println(p1); p1.setName("李四"); System.out.println(p); System.out.println(p1); } } |
輸出結果如下
Person{name=’張三’, age=18, height=178.0, weight=65.0}
Person{name=’張三’, age=18, height=178.0, weight=65.0}
Person{name=’張三’, age=18, height=178.0, weight=65.0}
Person{name=’李四’, age=18, height=178.0, weight=65.0}
試想一下,兩個不同的人,除了姓名不一樣,其他三個屬性都一樣,用原型模式進行拷貝就會顯得異常簡單,這也是原型模式的應用場景之一。
一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用,即保護性拷貝。
但是假設Person類裡還有一個屬性叫興趣愛好,是一個List集合,就像這樣子
1 2 3 4 5 6 7 8 9 |
private ArrayList<String> hobbies=new ArrayList<String>(); public ArrayList<String> getHobbies() { return hobbies; } public void setHobbies(ArrayList<String> hobbies) { this.hobbies = hobbies; } |
在進行拷貝的時候要格外注意,如果你直接按之前的程式碼那樣拷貝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Override public Object clone(){ Person person=null; try { person=(Person)super.clone(); person.name=this.name; person.weight=this.weight; person.height=this.height; person.age=this.age; person.hobbies=this.hobbies; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return person; } |
會帶來一個問題
使用測試程式碼進行測試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class Main { public static void main(String [] args){ Person p=new Person(); p.setAge(18); p.setName("張三"); p.setHeight(178); p.setWeight(65); ArrayList <String> hobbies=new ArrayList<String>(); hobbies.add("籃球"); hobbies.add("程式設計"); hobbies.add("長跑"); p.setHobbies(hobbies); System.out.println(p); Person p1= (Person) p.clone(); System.out.println(p1); p1.setName("李四"); p1.getHobbies().add("游泳"); System.out.println(p); System.out.println(p1); } } |
我們拷貝了一個物件,並添加了一個興趣愛好進去,看下列印結果
Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 程式設計, 長跑]}
Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 程式設計, 長跑]}
Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 程式設計, 長跑, 游泳]}
Person{name=’李四’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 程式設計, 長跑, 游泳]}
你會發現原來的物件的hobby也發生了變換。
其實導致這個問題的本質原因是我們只進行了淺拷貝,也就是隻拷貝了引用,最終兩個物件指向的引用是同一個,一個發生變化另一個也會發生變換,顯然解決方法就是使用深拷貝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Override public Object clone(){ Person person=null; try { person=(Person)super.clone(); person.name=this.name; person.weight=this.weight; person.height=this.height; person.age=this.age; person.hobbies=(ArrayList<String>)this.hobbies.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return person; } |
注意person.hobbies=(ArrayList)this.hobbies.clone();,不再是直接引用而是進行了一份拷貝。再執行一下,就會發現原來的物件不會再發生變化了。
Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 程式設計, 長跑]}
Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 程式設計, 長跑]}
Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 程式設計, 長跑]}
Person{name=’李四’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 程式設計, 長跑, 游泳]}
其實有時候我們會更多的看到原型模式的另一種寫法。
- 在clone函式裡呼叫建構函式,建構函式的入參是該類物件。
1 2 3 4 |
@Override public Object clone(){ return new Person(this); } |
- 在建構函式中完成拷貝邏輯
1 2 3 4 5 6 7 |
public Person(Person person){ this.name=person.name; this.weight=person.weight; this.height=person.height; this.age=person.age; this.hobbies= new ArrayList<String>(hobbies); } |
其實都差不多,只是寫法不一樣。
現在來挖挖android中的原型模式。
先看Bundle類,該類實現了Cloneable介面
1 2 3 4 5 6 7 8 9 |
public Object clone() { return new Bundle(this); } public Bundle(Bundle b) { super(b); mHasFds = b.mHasFds; mFdsKnown = b.mFdsKnown; } |
然後是Intent類,該類也實現了Cloneable介面
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 |
@Override public Object clone() { return new Intent(this); } public Intent(Intent o) { this.mAction = o.mAction; this.mData = o.mData; this.mType = o.mType; this.mPackage = o.mPackage; this.mComponent = o.mComponent; this.mFlags = o.mFlags; this.mContentUserHint = o.mContentUserHint; if (o.mCategories != null) { this.mCategories = new ArraySet<String>(o.mCategories); } if (o.mExtras != null) { this.mExtras = new Bundle(o.mExtras); } if (o.mSourceBounds != null) { this.mSourceBounds = new Rect(o.mSourceBounds); } if (o.mSelector != null) { this.mSelector = new Intent(o.mSelector); } if (o.mClipData != null) { this.mClipData = new ClipData(o.mClipData); } } |
用法也顯得十分簡單,一旦我們要用的Intent與現有的一個Intent很多東西都是一樣的,那我們就可以直接拷貝現有的Intent,再修改不同的地方,便可以直接使用。
1 2 3 4 5 6 |
Uri uri = Uri.parse("smsto:10086"); Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri); shareIntent.putExtra("sms_body", "hello"); Intent intent = (Intent)shareIntent.clone() ; startActivity(intent); |
網路請求中一個最常見的開源庫OkHttp中,也應用了原型模式。它就在OkHttpClient這個類中,它實現了Cloneable介面
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 |
/** Returns a shallow copy of this OkHttpClient. */ @Override public OkHttpClient clone() { return new OkHttpClient(this); } private OkHttpClient(OkHttpClient okHttpClient) { this.routeDatabase = okHttpClient.routeDatabase; this.dispatcher = okHttpClient.dispatcher; this.proxy = okHttpClient.proxy; this.protocols = okHttpClient.protocols; this.connectionSpecs = okHttpClient.connectionSpecs; this.interceptors.addAll(okHttpClient.interceptors); this.networkInterceptors.addAll(okHttpClient.networkInterceptors); this.proxySelector = okHttpClient.proxySelector; this.cookieHandler = okHttpClient.cookieHandler; this.cache = okHttpClient.cache; this.internalCache = cache != null ? cache.internalCache : okHttpClient.internalCache; this.socketFactory = okHttpClient.socketFactory; this.sslSocketFactory = okHttpClient.sslSocketFactory; this.hostnameVerifier = okHttpClient.hostnameVerifier; this.certificatePinner = okHttpClient.certificatePinner; this.authenticator = okHttpClient.authenticator; this.connectionPool = okHttpClient.connectionPool; this.network = okHttpClient.network; this.followSslRedirects = okHttpClient.followSslRedirects; this.followRedirects = okHttpClient.followRedirects; this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure; this.connectTimeout = okHttpClient.connectTimeout; this.readTimeout = okHttpClient.readTimeout; this.writeTimeout = okHttpClient.writeTimeout; } |
正如開頭的註釋Returns a shallow copy of this OkHttpClient,該clone方法返回了一個當前物件的淺拷貝物件。
至於其他框架中的原型模式,請讀者自行發現。
策略模式
看下策略模式的定義
策略模式定義了一些列的演算法,並將每一個演算法封裝起來,而且使它們還可以相互替換。策略模式讓演算法獨立於使用它的客戶而獨立變換。
乍一看,也沒看出個所以然來。舉個栗子吧。
假設我們要出去旅遊,而去旅遊出行的方式有很多,有步行,有坐火車,有坐飛機等等。而如果不使用任何模式,我們的程式碼可能就是這樣子的。
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 |
public class TravelStrategy { enum Strategy{ WALK,PLANE,SUBWAY } private Strategy strategy; public TravelStrategy(Strategy strategy){ this.strategy=strategy; } public void travel(){ if(strategy==Strategy.WALK){ print("walk"); }else if(strategy==Strategy.PLANE){ print("plane"); }else if(strategy==Strategy.SUBWAY){ print("subway"); } } public void print(String str){ System.out.println("出行旅遊的方式為:"+str); } public static void main(String[] args) { TravelStrategy walk=new TravelStrategy(Strategy.WALK); walk.travel(); TravelStrategy plane=new TravelStrategy(Strategy.PLANE); plane.travel(); TravelStrategy subway=new TravelStrategy(Strategy.SUBWAY); subway.travel(); } } |
這樣做有一個致命的缺點,一旦出行的方式要增加,我們就不得不增加新的else if語句,而這違反了面向物件的原則之一,對修改封閉。而這時候,策略模式則可以完美的解決這一切。
首先,需要定義一個策略介面。
1 2 3 4 |
public interface Strategy { void travel(); } |
然後根據不同的出行方式實行對應的介面
1 2 3 4 5 6 7 8 |
public class WalkStrategy implements Strategy{ @Override public void travel() { System.out.println("walk"); } } |
1 2 3 4 5 6 7 8 |
public class PlaneStrategy implements Strategy{ @Override public void travel() { System.out.println("plane"); } } |
1 2 3 4 5 6 7 8 |
public class SubwayStrategy implements Strategy{ @Override public void travel() { System.out.println("subway"); } } |
此外還需要一個包裝策略的類,並呼叫策略介面中的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class TravelContext { Strategy strategy; public Strategy getStrategy() { return strategy; } public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void travel() { if (strategy != null) { strategy.travel(); } } } |
測試一下程式碼
1 2 3 4 5 6 7 8 9 10 11 |
public class Main { public static void main(String[] args) { TravelContext travelContext=new TravelContext(); travelContext.setStrategy(new PlaneStrategy()); travelContext.travel(); travelContext.setStrategy(new WalkStrategy()); travelContext.travel(); travelContext.setStrategy(new SubwayStrategy()); travelContext.travel(); } } |
輸出結果如下
plane
walk
subway
可以看到,應用了策略模式後,如果我們想增加新的出行方式,完全不必要修改現有的類,我們只需要實現策略介面即可,這就是面向物件中的對擴充套件開放準則。假設現在我們增加了一種自行車出行的方式。只需新增一個類即可。
1 2 3 4 5 6 7 8 |
public class BikeStrategy implements Strategy{ @Override public void travel() { System.out.println("bike"); } } |
之後設定策略即可
1 2 3 4 5 6 7 |
public class Main { public static void main(String[] args) { TravelContext travelContext=new TravelContext(); travelContext.setStrategy(new BikeStrategy()); travelContext.travel(); } } |
而在Android的系統原始碼中,策略模式也是應用的相當廣泛的.最典型的就是屬性動畫中的應用.
我們知道,在屬性動畫中,有一個東西叫做插值器,它的作用就是根據時間流逝的百分比來來計算出當前屬性值改變的百分比.
我們使用屬性動畫的時候,可以通過set方法對插值器進行設定.可以看到內部維持了一個時間插值器的引用,並設定了getter和setter方法,預設情況下是先加速後減速的插值器,set方法如果傳入的是null,則是線性插值器。而時間插值器TimeInterpolator是個介面,有一個介面繼承了該介面,就是Interpolator這個介面,其作用是為了保持相容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private static final TimeInterpolator sDefaultInterpolator = new AccelerateDecelerateInterpolator(); private TimeInterpolator mInterpolator = sDefaultInterpolator; @Override public void setInterpolator(TimeInterpolator value) { if (value != null) { mInterpolator = value; } else { mInterpolator = new LinearInterpolator(); } } @Override public TimeInterpolator getInterpolator() { return mInterpolator; } |
1 2 3 4 5 6 |
public interface Interpolator extends TimeInterpolator { // A new interface, TimeInterpolator, was introduced for the new android.animation // package. This older Interpolator interface extends TimeInterpolator so that users of // the new Animator-based animations can use either the old Interpolator implementations or // new classes that implement TimeInterpolator directly. } |
此外還有一個BaseInterpolator插值器實現了Interpolator介面,並且是一個抽象類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
abstract public class BaseInterpolator implements Interpolator { private int mChangingConfiguration; /** * @hide */ public int getChangingConfiguration() { return mChangingConfiguration; } /** * @hide */ void setChangingConfiguration(int changingConfiguration) { mChangingConfiguration = changingConfiguration; } } |
平時我們使用的時候,通過設定不同的插值器,實現不同的動畫速率變換效果,比如線性變換,回彈,自由落體等等。這些都是插值器介面的具體實現,也就是具體的插值器策略。我們略微來看幾個策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { public LinearInterpolator() { } public LinearInterpolator(Context context, AttributeSet attrs) { } public float getInterpolation(float input) { return input; } /** @hide */ @Override public long createNativeInterpolator() { return NativeInterpolatorFactoryHelper.createLinearInterpolator(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class AccelerateDecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { public AccelerateDecelerateInterpolator() { } @SuppressWarnings({"UnusedDeclaration"}) public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) { } public float getInterpolation(float input) { return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; } /** @hide */ @Override public long createNativeInterpolator() { return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator(); } } |
內部使用的時候直接呼叫getInterpolation方法就可以返回對應的值了,也就是屬性值改變的百分比。
屬性動畫中另外一個應用策略模式的地方就是估值器,它的作用是根據當前屬性改變的百分比來計算改變後的屬性值。該屬性和插值器是類似的,有幾個預設的實現。其中TypeEvaluator是一個介面。
1 2 3 4 5 |
public interface TypeEvaluator<T> { public T evaluate(float fraction, T startValue, T endValue); } |
1 2 3 4 5 6 7 |
public class IntEvaluator implements TypeEvaluator<Integer> { public Integer evaluate(float fraction, Integer startValue, Integer endValue) { int startInt = startValue; return (int)(startInt + fraction * (endValue - startInt)); } } |
1 2 3 4 5 6 7 |
public class FloatEvaluator implements TypeEvaluator<Number> { public Float evaluate(float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); } } |
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 |
public class PointFEvaluator implements TypeEvaluator<PointF> { private PointF mPoint; public PointFEvaluator() { } public PointFEvaluator(PointF reuse) { mPoint = reuse; } @Override public PointF evaluate(float fraction, PointF startValue, PointF endValue) { float x = startValue.x + (fraction * (endValue.x - startValue.x)); float y = startValue.y + (fraction * (endValue.y - startValue.y)); if (mPoint != null) { mPoint.set(x, y); return mPoint; } else { return new PointF(x, y); } } } |
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 |
public class ArgbEvaluator implements TypeEvaluator { private static final ArgbEvaluator sInstance = new ArgbEvaluator(); public static ArgbEvaluator getInstance() { return sInstance; } public Object evaluate(float fraction, Object startValue, Object endValue) { int startInt = (Integer) startValue; int startA = (startInt >> 24) & 0xff; int startR = (startInt >> 16) & 0xff; int startG = (startInt >> 8) & 0xff; int startB = startInt & 0xff; int endInt = (Integer) endValue; int endA = (endInt >> 24) & 0xff; int endR = (endInt >> 16) & 0xff; int endG = (endInt >> 8) & 0xff; int endB = endInt & 0xff; return (int)((startA + (int)(fraction * (endA - startA))) << 24) | (int)((startR + (int)(fraction * (endR - startR))) << 16) | (int)((startG + (int)(fraction * (endG - startG))) << 8) | (int)((startB + (int)(fraction * (endB - startB)))); } } |
上面的都是一些系統實現好的估值策略,在內部呼叫估值器的evaluate方法即可返回改變後的值了。我們也可以自定義估值策略。這裡就不展開了。
當然,在開源框架中,策略模式也是無處不在的。
首先在Volley中,策略模式就能看到。
有一個重試策略介面
1 2 3 4 5 6 7 8 9 10 11 |
public interface RetryPolicy { public int getCurrentTimeout();//獲取當前請求用時(用於 Log) public int getCurrentRetryCount();//獲取已經重試的次數(用於 Log) public void retry(VolleyError error) throws VolleyError;//確定是否重試,引數為這次異常的具體資訊。在請求異常時此介面會被呼叫,可在此函式實現中丟擲傳入的異常表示停止重試。 } |
在Volley中,該介面有一個預設的實現DefaultRetryPolicy,Volley 預設的重試策略實現類。主要通過在 retry(…) 函式中判斷重試次數是否達到上限確定是否繼續重試。
1 2 3 |
public class DefaultRetryPolicy implements RetryPolicy { ... } |
而策略的設定是在Request類中
1 2 3 4 5 6 7 8 9 10 11 |
public abstract class Request<T> implements Comparable<Request<T>> { private RetryPolicy mRetryPolicy; public Request<?> setRetryPolicy(RetryPolicy retryPolicy) { mRetryPolicy = retryPolicy; return this; } public RetryPolicy getRetryPolicy() { return mRetryPolicy; } } |
此外,各大網路請求框架,或多或少都會使用到快取,快取一般會定義一個Cache介面,然後實現不同的快取策略,如記憶體快取,磁碟快取等等,這個快取的實現,其實也可以使用策略模式。直接看Volley,裡面也有快取。
定義了一個快取介面
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
/** * An interface for a cache keyed by a String with a byte array as data. */ public interface Cache { /** * Retrieves an entry from the cache. * @param key Cache key * @return An {@link Entry} or null in the event of a cache miss */ public Entry get(String key); /** * Adds or replaces an entry to the cache. * @param key Cache key * @param entry Data to store and metadata for cache coherency, TTL, etc. */ public void put(String key, Entry entry); /** * Performs any potentially long-running actions needed to initialize the cache; * will be called from a worker thread. */ public void initialize(); /** * Invalidates an entry in the cache. * @param key Cache key * @param fullExpire True to fully expire the entry, false to soft expire */ public void invalidate(String key, boolean fullExpire); /** * Removes an entry from the cache. * @param key Cache key */ public void remove(String key); /** * Empties the cache. */ public void clear(); /** * Data and metadata for an entry returned by the cache. */ public static class Entry { /** The data returned from cache. */ public byte[] data; /** ETag for cache coherency. */ public String etag; /** Date of this response as reported by the server. */ public long serverDate; /** The last modified date for the requested object. */ public long lastModified; /** TTL for this record. */ public long ttl; /** Soft TTL for this record. */ public long softTtl; /** Immutable response headers as received from server; must be non-null. */ public Map<String, String> responseHeaders = Collections.emptyMap(); /** True if the entry is expired. */ public boolean isExpired() { return this.ttl < System.currentTimeMillis(); } /** True if a refresh is needed from the original data source. */ public boolean refreshNeeded() { return this.softTtl < System.currentTimeMillis(); } } } |
它有兩個實現類NoCache和DiskBasedCache,使用的時候設定對應的快取策略即可。
在android開發中,ViewPager是一個使用非常常見的控制元件,它的使用往往需要伴隨一個Indicator指示器。如果讓你重新實現一個ViewPager,並且帶有Indicator,這時候,你會不會想到用策略模式呢?在你自己寫的ViewPager中(不是系統的,當然你也可以繼承系統的)持有一個策略介面Indicator的變數,通過set方法設定策略,然後ViewPager滑動的時候呼叫策略介面的對應方法改變指示器。預設提供幾個Indicator介面的實現類,比如圓形指示器CircleIndicator、線性指示器LineIndicator、Tab指示器TabIndicator、圖示指示器IconIndicator 等等等等。有興趣的話自己去實現一個吧。