談談 23 種設計模式在 Android 專案中的應用

林炳河發表於2017-03-13

前言

本文將結合實際談談23種設計模式,每種設計模式涉及

  • 定義:抽象化的定義與通俗的描述,儘量說明清楚其含義與應用場景
  • 示例:如果專案中有使用過該模式,則會給出專案中的程式碼,否則會給出儘可能簡單好理解的java程式碼
  • Android:該設計模式在Android原始碼框架中哪些地方有使用到
  • 重構:專案中是否存在可以用該模式進行重構的地方,如果有會給出重構前與重構後的程式碼或者思路

用這種方式進行介紹設計模式,旨在結合每天都在接觸的Android實際專案開發更好地理解設計模式,拉近與設計模式的距離,同時在實際開發與重構中,思考可以應用的重構手段與設計模式,既能保證寫出複用性與可靠性更高的程式碼,也是對如何利用重構與設計模式這兩大支柱進行優雅程式設計的最佳實踐與總結。

同時一次性以這種方式介紹23種設計模式,也是出於既然要使用一個模式,那麼就應該要先知道這麼一個模式的想法,四人幫的《設計模式》也是對經驗的總結,但是有巨人託著你上去,又何必自己再摸黑造梯子。

重構不是本章的重點,因為這也是一個非常大的話題,這邊只討論實際專案中是否有存在一些能用設計模式進行改善的地方。
關於重構,這邊也有寫了一篇博文 重構:改善既有程式碼的設計 ,基本列舉了《重構:改善既有程式碼的設計》中的各項要點,後續還會繼續將《重構》中的手法與設計模式應用到實際專案中,有所總結之後會再寫幾篇實際應用的博文。

簡介

設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。使用設計模式是為了可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使程式碼編制真正工程化,設計模式是軟體工程的基石,如同大廈的一塊塊磚石一樣。專案中合理的運用設計模式可以完美的解決很多問題,每種模式在現在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。

六大原則

單一職責原則

單一原則很簡單,就是將一組相關性很高的函式、資料封裝到一個類中。換句話說,一個類應該有職責單一。

開閉原則

開閉原則理解起來也不復雜,就是一個類應該對於擴充套件是開放的,但是對於修改是封閉的。在一開始編寫程式碼時,就應該注意儘量通過擴充套件的方式實現新的功能,而不是通過修改已有的程式碼實現,否則容易破壞原有的系統,也可能帶來新的問題,如果發現沒辦法通過擴充套件來實現,應該考慮是否是程式碼結構上的問題,通過重構等方式進行解決。

里氏替換原則

所有引用基類的地方必須能透明地使用其子類物件。本質上就是說要好好利用繼承和多型,從而以父類的形式來宣告變數(或形參),為變數(或形參)賦值任何繼承於這個父類的子類。

依賴倒置原則

依賴倒置主要是實現解耦,使得高層次的模組不依賴於低層次模組的具體實現細節。怎麼去理解它呢,我們需要知道幾個關鍵點:

  • 高層模組不應該依賴底層模組(具體實現),二者都應該依賴其抽象(抽象類或介面)
  • 抽象不應該依賴細節
  • 細節應該依賴於抽象

在我們用的Java語言中,抽象就是指介面或者抽象類,二者都是不能直接被例項化;細節就是實現類,實現介面或者繼承抽象類而產生的類,就是細節。使用Java語言描述就是:各個模組之間相互傳遞的引數宣告為抽象型別,而不是宣告為具體的實現類;

介面隔離原則

類之間的依賴關係應該建立在最小的介面上。其原則是將非常龐大的、臃腫的介面拆分成更小的更具體的介面。

迪米特原則

一個物件應該對其他的物件有最少的瞭解.

假設類A實現了某個功能,類B需要呼叫類A的去執行這個功能,那麼類A應該只暴露一個函式給類B,這個函式表示是實現這個功能的函式,而不是讓類A把實現這個功能的所有細分的函式暴露給B。

設計模式

單例模式

定義

確保單例類只有一個例項,並且這個單例類提供一個函式介面讓其他類獲取到這個唯一的例項。
如果某個類,建立時需要消耗很多資源,即new出這個類的代價很大;或者是這個類佔用很多記憶體,如果建立太多這個類例項會導致記憶體佔用太多。上述情況下就應該使用單例模式

實際應用

   // 單例物件
    private static AdvertPresenter mInstance;
    /**
     * 私有化建構函式
     */
    private AdvertPresenter(){
    }
    /**
     * 獲取AdvertPresenter例項
     * @return
     */
    public static AdvertPresenter getInstance() {
        if (mInstance == null) {
            synchronized (AdvertPresenter.class) {
                if (mInstance == null) {
                    mInstance = new AdvertPresenter();
                }
            }
        }
        return mInstance;
    }

Android

//獲取WindowManager服務引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);

其內部就是通過單例的方式持有一個WindowManager並返回這個物件

重構

專案中存在多次使用Random與Gson的操作,可以將Random與Gson物件封裝成單例進行使用

建造者模式

定義

將一個複雜物件的構造與它的表示分離,使得同樣的構造過程可以建立不同的表示。
主要是在建立某個物件時,需要設定很多的引數(通過setter方法),但是這些引數必須按照某個順序設定,或者是設定步驟不同會得到不同結果。

示例

各類自定義Dialog

Android

AlertDialog.Builer builder=new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon)
    .setTitle("title")
    .setMessage("message")
    .setPositiveButton("Button1",
        new DialogInterface.OnclickListener(){
            public void onClick(DialogInterface dialog,int whichButton){
                setTitle("click");
            }   
        })
    .create()
    .show();

重構

暫無

原型模式

定義

用原型例項指定建立物件的種類,並通過拷貝這些原型建立新的物件。
可以在類的屬性特別多,但是又要經常對類進行拷貝的時候可以用原型模式,這樣程式碼比較簡潔,而且比較方便。
拷貝時要注意淺拷貝與深拷貝

示例

     private HashMap getClonePointMap(Map map) {
            HashMap clone = new HashMap<>();
            if (map != null) {
                Iterator iterator = map.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry entry = (Map.Entry) iterator.next();
                    String key = (String) entry.getKey();
                    PointBean pointBean = (PointBean) entry.getValue();
                    if (pointBean != null) {
                        //遍歷map並將克隆物件放到新的map中
                        clone.put(key, pointBean.clone());
                    } else {
                        clone.put(key, null);
                    }
                }
            }
            return clone;
        }

Android

Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
//克隆副本
Intent copyIntent=(Intetn)shareIntent.clone();

重構

如果存在逐一去除某個物件的各項引數值,轉而賦值給另一個物件身上時,便可使用原型模式

工廠模式

簡單工廠模式

定義

建立一個工廠(一個函式或一個類方法)來製造新的物件。

示例

public static Operation createOperate(string operate)
{
    Operation oper = null;
    switch (operate)
    {
        case "+":
            {
            oper = new OperationAdd();
            break;
            }
        case "-":
            {
            oper = new OperationSub();
            break;
            }
        case "*":
            {
            oper = new OperationMul();
            break;
            }
        case "/":
            {
            oper = new OperationDiv();
            break;
            }
    }
    return oper;
   }
}

Android

public Object getSystemService(String name) {
    if (getBaseContext() == null) {
        throw new IllegalStateException("System services not available to Activities before onCreate()");
    }
    //........
    if (WINDOW_SERVICE.equals(name)) {
         return mWindowManager;
    } else if (SEARCH_SERVICE.equals(name)) {
        ensureSearchManager();
        return mSearchManager;
    }
    //.......
    return super.getSystemService(name);
  }

在getSystemService方法中就是用到了簡單工廠模式,根據傳入的引數決定建立哪個物件,由於這些物件以單例模式提前建立好了,所以此處不用new了,直接把單例返回就好。

重構

//重構前
public class AdvertPresenter {
    ...
    private void initAdvertManager() {
        String[] platforms = mAdConfig.getAllPlatforms();
        if (platforms != null && platforms.length > 0) {
            int platformSize = platforms.length;
            for (int i = 0; i < platformSize; i++) {
                String platform = platforms[i];
                if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_FACEBOOK)) {
                    FacebookAdvertManager fbAdManager = new FacebookAdvertManager();
                    mAdvertManager.put(AdvertConstant.AD_PLATFORM_FACEBOOK, fbAdManager);
                } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_ADMOB)) {
                    AdMobAdvertManager adMobAdvertManager = new AdMobAdvertManager();
                    mAdvertManager.put(AdvertConstant.AD_PLATFORM_ADMOB, adMobAdvertManager);
                } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_MOPUB)) {
                    MopubAdvertManager mopubAdvertManager = new MopubAdvertManager();
                    mAdvertManager.put(AdvertConstant.AD_PLATFORM_MOPUB, mopubAdvertManager);
                } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_ADX)) {
                    AdxAdvertManager mopubAdvertManager = new AdxAdvertManager();
                    mAdvertManager.put(AdvertConstant.AD_PLATFORM_ADX, mopubAdvertManager);
                }
            }
        }
    }
    ...
}
//重構後
public class BaseAdvertManager {
    ...
    public static BaseAdvertManager create(String platform) {
        if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_FACEBOOK)) {
            return new FacebookAdvertManager();
        } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_MOPUB)) {
            return new MopubAdvertManager();
        } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_ADX)) {
            return new AdxAdvertManager();
        } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_ADMOB)) {
            return new AdMobAdvertManager();
        } else {
            ***return new NullAdvertManager();***//引入NULL物件
        }
    }
    ...
}
public class AdvertPresenter {
    ...
    private void initAdvertManager() {
        String[] platforms = mAdConfig.getAllPlatforms();
        if (platforms != null && platforms.length > 0) {
            int platformSize = platforms.length;
            for (int i = 0; i < platformSize; i++) {
                String platform = platforms[i];
                mAdvertManager.put(platform, BaseAdvertManager.create(platform));
            }
        }
    }
    ...
}

工廠方法模式

定義

是定義一個建立產品物件的工廠介面,讓其子類決定例項化哪一個類,將實際建立工作推遲到子類當中。

示例

public abstract class Product {
    public abstract void method();
}

public class ConcreteProduct extends Prodect {
    public void method(){
        System.out.println("我是具體產品!");
    }
}

public  abstract class Factory{
    public abstract Product createProduct();
}

public class ConcreteFactory extends Factory{

    public Product createProduct(){
        return new ConcreteProductA();
    }
}

Android

我們在開發中會用到很多資料結構,比如ArrayList,HashMap等。我們先來看下Java中Collection部分的類集框架的簡要UML圖。

我們知道Iterator是迭代器,用來遍歷一個集合中的元素。而不同的資料結構遍歷的方式是不一樣的,所以迭代器的實現也是不同的。使用工廠方法模式將迭代器的具體型別延遲到具體容器類中,比較靈活,容易擴充套件。
public interface Iterable {
    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator iterator();

    //省略部分程式碼
}

List和Set繼承自Collection介面,Collection介面繼承於Iterable介面。所以List和Set介面也需要繼承並實現Iterable中的iterator()方法。然後我們常用的兩個間接實現類ArrayList和HashSet中的iterator方法就給我們具體構造並返回了一個迭代器物件。
我們找到ArrayList類,檢視iterator方法的實現。

@Override
public Iterator iterator() {
    return new ArrayListIterator();
}

ArrayListIterator型別定義如下:

private class ArrayListIterator implements Iterator {
    /** Number of elements remaining in this iteration */
    private int remaining = size;

    /** Index of element that remove() would remove, or -1 if no such elt */
    private int removalIndex = -1;

    /** The expected modCount value */
    private int expectedModCount = modCount;

    public boolean hasNext() {
        return remaining != 0;
    }

    @SuppressWarnings("unchecked") public E next() {
        ArrayList ourList = ArrayList.this;
        int rem = remaining;
        if (ourList.modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        if (rem == 0) {
            throw new NoSuchElementException();
        }
        remaining = rem - 1;
        return (E) ourList.array[removalIndex = ourList.size - rem];
    }

    public void remove() {
        Object[] a = array;
        int removalIdx = removalIndex;
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        if (removalIdx < 0) {
            throw new IllegalStateException();
        }
        System.arraycopy(a, removalIdx + 1, a, removalIdx, remaining);
        a[--size] = null;  // Prevent memory leak
        removalIndex = -1;
        expectedModCount = ++modCount;
    }
}

我們看到這個類實現了Iterator介面,介面的定義如下:

public interface Iterator {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

基本的結構也分析完了,接下來對號入座,看一看具體是如何實現工廠方法模式的。
Iterator————>Product ArrayListIteratorr————>ConcreteProduct
Iterable/List————>Factory ArrayList————>ConcreteFactory
工廠方法使一個類的例項化延遲到子類,對應著將迭代器Iterator的建立從List延遲到了ArrayList。這就是工廠方法模式。

重構

暫無

抽象工廠模式

定義

為建立一組相關或者是相互依賴的物件提供一個介面,而不需要制定他們的具體類
抽象工廠模式是指當有多個抽象角色時,使用的一種工廠模式。抽象工廠模式可以向客戶端提供一個介面,使客戶端在不必指定產品的具體情況下,建立多個產品族中的產品物件。

示例

public abstract class AbstractProductA{
    public abstract void method();
}
public abstract class AbstractProdectB{
    public abstract void method();
}

public class ConcreteProductA1 extends AbstractProductA{
    public void method(){
        System.out.println("具體產品A1的方法!");
    }
}
public class ConcreteProductA2 extends AbstractProductA{
    public void method(){
        System.out.println("具體產品A2的方法!");
    }
}
public class ConcreteProductB1 extends AbstractProductB{
    public void method(){
        System.out.println("具體產品B1的方法!");
    }
}
public class ConcreteProductB2 extends AbstractProductB{
    public void method(){
        System.out.println("具體產品B2的方法!");
    }
}

public abstract class AbstractFactory{
    public abstract AbstractProductA createProductA();

    public abstract AbstractProductB createProductB();
}

public  class ConcreteFactory1 extends AbstractFactory{
    public  AbstractProductA createProductA(){
        return new ConcreteProductA1();
    }

    public  AbstractProductB createProductB(){
        return new ConcreteProductB1();
    }
}

public  class ConcreteFactory2 extends AbstractFactory{
    public  AbstractProductA createProductA(){
        return new ConcreteProductA2();
    }

    public  AbstractProductB createProductB(){
        return new ConcreteProductB2();
    }
}

Android

由於該模式存在的侷限性,Android中很少有用到這個模式的地方,com.android.internal.policy包下的IPolicy有使用到這個模式,它是關於Android視窗,視窗管理,佈局載入,以及事件回退Handler這一系列視窗相關產品的抽象工廠,但是其在原始碼中其實也只有一個具體的工廠實現。因為這部分結構較為複雜,程式碼量大,有興趣的同學可以自己去檢視相關資料或者閱讀原始碼。

與工廠方法模式對比

使用

  • 不依賴於產品類例項如何被建立,組合和表達的細節;
  • 產品有多於一個的產品族,而系統只消費其中某一族的產品;
  • 同屬於同一個產品族是在一起使用的;
  • 提供一個產品類的庫,所有產品以同樣的介面出現,從而使使用者不依賴於實現;

區別

  • 抽象工廠是面向一個工廠方法的升級;
  • 抽象方法提供的是一個產品族,即多個產品等級結構,而工廠方法則是針對一個產品等級結構;
  • 抽象方法提供的產品是衍生自多個抽象或者介面,而工廠方法則衍生自同一個抽象或者介面;

優點

  • 抽象工廠模式隔離了具體類的生產,使得客戶並不需要知道什麼被建立。
  • 當一個產品族中的多個物件被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的物件。
  • 增加新的具體工廠和產品族很方便,無須修改已有系統,符合“開閉原則”。

缺點

  • 增加新的產品等級結構很複雜,需要修改抽象工廠和所有的具體工廠類,對“開閉原則”的支援呈現傾斜性。
  • (可以把示例中的AB當做等級,12當做族群,A1B1屬於同一族群不同等級,當新增同一等級下新的產品時很方便,但是要新增不同等級的產品就會破壞“開閉原則”)

由於抽象工廠不易於擴充新的產品族,所以這種設計模式,在提供對外部人員訪問時,很少使用,也有人說抽象工廠方法模式是一種很“噁心”的設計模式,運用最為典範的一個是該模式最初的目的,也就是為了適應Unit和Windows兩個作業系統下的檢視而構建檢視族,檢視族有各自不同的實現;另一個就是Java連線資料庫的操作中,對不同的資料庫的操作而形成的的物件操作族,但是當再次更換資料時,所需要造成的介面的修改也十分麻煩,所以擴充套件性不好

重構

暫無

策略模式

定義

有一系列的演算法,將每個演算法封裝起來(每個演算法可以封裝到不同的類中),各個演算法之間可以替換,策略模式讓演算法獨立於使用它的客戶而獨立變化。

示例

public abstract class BaseAdvertManager {
    protected abstract void doLoadAdvert();
}

public class FacebookAdvertManager extends BaseAdvertManager {
 @Override
    protected void doLoadAdvert() {
        Log.v(TAG, "載入Facebook廣告");
    }
}

public class AdmobAdvertManager extends BaseAdvertManager {
 @Override
    protected void doLoadAdvert() {
        Log.v(TAG, "載入Admob廣告");
    }
}

Android

Android在屬性動畫中使用時間插值器的時候就用到了策略模式。在使用動畫時,你可以選擇線性插值器LinearInterpolator、加速減速插值器AccelerateDecelerateInterpolator、減速插值器DecelerateInterpolator以及自定義的插值器。這些插值器都是實現根據時間流逝的百分比來計算出當前屬性值改變的百分比。通過根據需要選擇不同的插值器,實現不同的動畫效果。

重構

暫無

狀態模式

定義

狀態模式中,行為是由狀態來決定的,不同狀態下有不同行為。狀態模式和策略模式的結構幾乎是一模一樣的,主要是他們表達的目的和本質是不同。

示例

public interface TvState{
    public void nextChannerl();
    public void prevChannerl();
    public void turnUp();
    public void turnDown();
}

public class PowerOffState implements TvState{
    public void nextChannel(){}
    public void prevChannel(){}
    public void turnUp(){}
    public void turnDown(){}

}

public class PowerOnState implements TvState{
    public void nextChannel(){
        System.out.println("下一頻道");
    }
    public void prevChannel(){
        System.out.println("上一頻道");
    }
    public void turnUp(){
        System.out.println("調高音量");
    }
    public void turnDown(){
        System.out.println("調低音量");
    }

}

public interface PowerController{
    public void powerOn();
    public void powerOff();
}

public class TvController implements PowerController{
    TvState mTvState;
    public void setTvState(TvStete tvState){
        mTvState=tvState;
    }
    public void powerOn(){
        setTvState(new PowerOnState());
        System.out.println("開機啦");
    }
    public void powerOff(){
        setTvState(new PowerOffState());
        System.out.println("關機啦");
    }
    public void nextChannel(){
        mTvState.nextChannel();
    }
    public void prevChannel(){
        mTvState.prevChannel();
    }
    public void turnUp(){
        mTvState.turnUp();
    }
    public void turnDown(){
        mTvState.turnDown();
    }
}

public class Client{
    public static void main(String[] args){
        TvController tvController=new TvController();
        tvController.powerOn();
        tvController.nextChannel();
        tvController.turnUp();

        tvController.powerOff();
        //調高音量,此時不會生效
        tvController.turnUp();
    }
}

Android

Android原始碼中很多地方都有用到狀態模式,舉一個例子,就是Android的WIFI管理模組。當WIFI開啟時,自動掃描周圍的接入點,然後以列表的形式展示;當wifi關閉時則清空。這裡wifi管理模組就是根據不同的狀態執行不同的行為。

與策略模式的區別

狀態模式的行為是平行的、不可替換的,策略模式是屬於物件的行為模式,其行為是彼此獨立可相互替換的。

重構

專案中有需要功能如瘦臉等存在開關,現在是通過配置檔案進行判斷,可以通過狀態模式進行重構,進而在具體處理圖片時可以利用多型的特性直接使用物件進行處理

責任鏈模式

定義

使多個物件都有機會處理請求,從而避免請求的傳送者和接受者直接的耦合關係,將這些物件連成一條鏈,並沿這條鏈傳遞該請求,直到有物件處理它為止。

示例

/**
 * 抽象處理者
 */
public abstract class Handler {

    /**
     * 持有後繼的責任物件
     */
    protected Handler successor;
    /**
     * 示意處理請求的方法,雖然這個示意方法是沒有傳入引數的
     * 但實際是可以傳入引數的,根據具體需要來選擇是否傳遞引數
     */
    public abstract void handleRequest();
    /**
     * 取值方法
     */
    public Handler getSuccessor() {
        return successor;
    }
    /**
     * 賦值方法,設定後繼的責任物件
     */
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

}

/**
 * 具體處理者
 */
public class ConcreteHandler extends Handler {
    /**
     * 處理方法,呼叫此方法處理請求
     */
    @Override
    public void handleRequest() {
        /**
         * 判斷是否有後繼的責任物件
         * 如果有,就轉發請求給後繼的責任物件
         * 如果沒有,則處理請求
         */
        if(getSuccessor() != null)
        {           
            System.out.println("放過請求");
            getSuccessor().handleRequest();           
        }else
        {           
            System.out.println("處理請求");
        }
    }

}

/**
 * 發起請求的客戶類
 */
public class Client {

    public static void main(String[] args) {
        //組裝責任鏈
        Handler handler1 = new ConcreteHandler();
        Handler handler2 = new ConcreteHandler();
        handler1.setSuccessor(handler2);
        //提交請求
        handler1.handleRequest();
    }

}

Android

在Android處理點選事件時,父View先接收到點選事件,如果父View不處理則交給子View,把責任依次往下傳遞;還有Java的異常捕獲機制也是責任鏈模式的一種體現

重構

暫無

直譯器模式

定義

給定一個語言,定義它的語法,並定義一個直譯器,這個直譯器用於解析語言。

示例

如編寫各種功能模組的配置檔案,然後按照專案定義的配置檔案編寫規則在執行過程中將配置檔案載入為配置物件,這個模式在日常專案中應該或多或少都會使用到,就不貼出程式碼了。

Android

這個用到的地方也不少,其一就是Android的四大元件需要在AndroidManifest.xml中定義,其實AndroidManifest.xml就定義了,等標籤(語句)的屬性以及其子標籤,規定了具體的使用(語法),通過PackageManagerService(直譯器)進行解析。

重構

暫無

命令模式

定義

命令模式將每個請求封裝成一個物件,從而讓使用者使用不同的請求把客戶端引數化;將請求進行排隊或者記錄請求日誌,以及支援可撤銷操作。
舉個例子來理解:當我們點選“關機”命令,系統會執行一系列操作,比如暫停事件處理、儲存系統配置、結束程式程式、呼叫核心命令關閉計算機等等,這些命令封裝從不同的物件,然後放入到佇列中一個個去執行,還可以提供撤銷操作。

示例

   public void method() {
        Handler.post(new Runnable() {
            @Override
            public void run() {
                clearCache();
                statics();
                finish();
            }
        });
    }

Android

在Android事件機制中,底層邏輯對事件的轉發處理。每次的按鍵事件會被封裝成NotifyKeyArgs物件,通過InputDispatcher封裝具體的事件操作。還有一個例子就是我們使用的Runnable,我們可以使用它來封裝自己想做的操作,然後交給Handler按順序處理,或者在處理前remove取消掉

重構

現在的廣告模組業務邏輯可以進行這種模式的重構,將廣告拉取、廣告展示等請求都封裝成一個個的物件,放入到一個管理容器中按照一定規則進行呼叫,這樣不僅可以避免在呼叫了展示廣告介面的時候正在拉取廣告,進而導致展示次數丟失的情況,也可以在出現網路錯誤等異常情況時可以對一些步驟進行取消。

觀察者模式

定義

有時被稱作釋出/訂閱模式,其定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。

示例

Java的Observable類和Observer介面就是實現了觀察者模式。一個Observer物件監視著一個Observable物件的變化,當Observable物件發生變化時,Observer得到通知,就可以進行相應的工作。
感興趣的可以直接去檢視兩個類的原始碼,這個模式比較常用,就不在這裡再貼一次了。

Android

ListView的介面卡有個notifyDataSetChange()函式,就是通知ListView的每個Item,資料來源發生了變化,各個子Item需要重新重新整理一下。

重構

比如現在相機選完濾鏡拍照後進入自拍確認頁,這時候如果使用者更換了濾鏡,那麼回來的時候應該把相機介面的濾鏡替換為最新選擇的濾鏡。目前是在相機介面的onResume函式裡做這個邏輯,但是其實可以把濾鏡抽成一個完整的模組,然後在模組內部自己實現觀察者模式,這樣一方面把濾鏡的邏輯封裝起來,與外界解耦,也符合Java多用組合的理念

EventBus

EventBus的好處很明顯,可以很方便簡單地實現觀察者模式,但是壞處也很明顯

  • 大量的濫用,將導致邏輯的分散,出現問題後很難定位。
  • 沒辦法實現強型別,在編譯的時候就發現問題,(Otto實現了這個,但效能有問題)。在實現上通過一個很弱的協議,比如onEvent{XXX}, {XXX}表示ThreadModel,來實現執行緒的切換。
  • 程式碼可讀性存在問題,IDE無法識別這些協議,對IDE不友好。

所以如果出現了需要使用觀察者模式的情況,在各方面條件允許的情況下,建議還是在這個模組中自己實現觀察者模式,如果發現這個功能在其他模組也需要,那麼就要考慮是不是應該把這系列功能抽成一個更加獨立的模組從而進行復用,而不是貪圖方便地直接使用EventBus

備忘錄模式

定義

在不破壞封閉的前提下,捕獲一個物件的內部狀態,並在物件之外儲存這個狀態,這樣,以後就可將物件恢復到原先儲存的狀態中。

示例

序列化物件到本地並在必要時將物件反序列化恢復回來

      public PhotoData readPhotoData(String path) {
            PhotoData photoData = null;
            ObjectInputStream objInput = null;
            try {
                objInput = new ObjectInputStream(new FileInputStream(path));
                photoData = (PhotoData) objInput.readObject();
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (objInput != null) {
                        objInput.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return photoData;
        }

    public void writePhotoData(PhotoData data, String path) {
        ObjectOutputStream objOutput = null;
        try {
            objOutput = new ObjectOutputStream(new FileOutputStream(path));
            objOutput.writeObject(data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (objOutput != null) {
                    objOutput.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

Android

Activity的onSaveInstanceState和onRestoreInstanceState就是用到了備忘錄模式,分別用於儲存和恢復。

重構

暫無

迭代器模式

定義

提供一種方法順序訪問一個容器物件中的各個元素,而不需要暴露該物件的內部表示。
迭代器模式是與集合共生共死的,一般來說,我們只要實現一個集合,就需要同時提供這個集合的迭代器,就像java中的Collection,List、Set、Map等,這些集合都有自己的迭代器。假如我們要實現一個這樣的新的容器,當然也需要引入迭代器模式,給我們的容器實現一個迭代器。

示例

Java的Iterator就是一個抽象迭代器,通過實現這個介面各個集合可以提供自己定製的具體迭代器,感興趣的可以直接檢視原始碼
雖然我們使用集合的場景非常多,但是實際使用到迭代器的卻比較少,對於比較簡單的遍歷(像陣列或者有序列表),使用迭代器方式遍歷較為繁瑣,比如ArrayList,我們更傾向於使用for迴圈來遍歷,但是針對hash表之類的集合來說,引入迭代器就反而簡單多了。同時我們也可以通過自定義迭代器來對有序列表提供正序遍歷或者倒序遍歷,使用者只需要得到迭代器就可以遍歷了,而不需要關心具體遍歷演算法。

Android

Android原始碼中,最典型的就是Cursor用到了迭代器模式,當我們使用SQLiteDatabase的query方法時,返回的就是Cursor物件,之後再通過Cursor去遍歷資料

重構

暫無

模板方法模式

定義

定義一個操作中的演算法框架,而將一些步驟延遲到子類中,使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定的步驟。

示例

public abstract class BaseAdvertManager {
    public void loadAdvert(Context context, int type) {
        if (NetUtils.checkNetConnection(context) != NetUtils.OK) {
            return;
        }
        mStateArray.put(type, AdvertConstant.AD_STATE_LOADING);
        doLoadAdvert(context, type);
    }

    protected abstract void doLoadAdvert(Context context, int type);
}

廣告管理器抽象類定義了載入廣告的通用模板,但是又把模板中具體載入廣告的邏輯宣告為抽象方法為各個子類自我實現

Android

啟動一個Activity過程非常複雜,有很多地方需要開發者定製,也就是說,整體演算法框架是相同的,但是將一些步驟延遲到子類中,比如Activity的onCreate、onStart等等。這樣子類不用改變整體啟動Activity過程即可重定義某些具體的操作了。

重構

大部分程式碼相同部分程式碼不同的方法都可以嘗試使用模板方法重構,要麼是在同一個類裡面進行方法的重構,要麼通過塑造模板函式的重構手段對子類與父類進行重構
專案中自拍確認頁儲存照片並分享與儲存照片並後退目前是兩個獨立的方法,但是方法內部大多數程式碼都是一樣的,就需要用該模式進行重構,由於程式碼量大而且該重構手段比較簡單,就不貼出程式碼

訪問者模式

定義

封裝一些作用於某種資料結構中各元素的操作,它可以在不改變這個資料結構的前提下定義作用於這些元素的新的操作。
假如一個物件中存在著一些與本物件不相干(或者關係較弱)的操作,為了避免這些操作汙染這個物件,則可以使用訪問者模式來把這些操作封裝到訪問者中去。
假如一組物件中,存在著相似的操作,為了避免出現大量重複的程式碼,也可以將這些重複的操作封裝到訪問者中去。
訪問者模式的目的是封裝一些施加於某種資料結構元素之上的操作,一旦這些操作需要修改的話,接受這個操作的資料結構則可以保持不變。
訪問者模式是23種設計模式中最複雜最難理解的一個,但他的使用率並不高,大部分情況下,我們不需要使用訪問者模式,少數特定的場景才需要。

大多數情況下,你並需要使用訪問者模式,但是當你一旦需要使用它時,那你就是真的需要它了。——GOF《設計模式:可複用物件導向軟體的基礎 》

示例

interface Service {

    public void accept(Visitor visitor);
}

class Draw implements Service {

    public void accept(Visitor visitor) {
        visitor.process(this);
    }
}

class Fund implements Service {

    public void accept(Visitor visitor) {
        visitor.process(this);
    }
}

class Saving implements Service {

    public void accept(Visitor visitor) {
        visitor.process(this);
    }
}

class Visitor {

    public void process(Service service) {
        // 基本業務
        System.out.println("基本業務");
    }

    public void process(Saving service) {
        // 存款
        System.out.println("存款");
    }

    public void process(Draw service) {
        // 提款
        System.out.println("提款");
    }

    public void process(Fund service) {
        System.out.println("基金");
        // 基金
    }
}

public class Client {
    public static void main(String[] args){
        Service saving = new Saving();
        Service fund = new Fund();
        Service draw = new Draw();

        Visitor visitor = new Visitor();
        saving.accept(visitor);
        fund.accept(visitor);
        draw.accept(visitor);
    }
}

採用Visitor的好處如上面所示,當需要改變其中一項業務的處理時,不需要每個地方都進行修改,而只需要改動Visitor類中相應的處理函式就可以了。也就是說它適合於業務處理時常發生變動的情況。
當然,Visitor也有它自身的限制。它不適合於業務數量的經常變化,因為一旦新增或刪除一些Service時,需要對Visitor進行相應的增刪。也就是說具體Service與Visitor是耦合的。

Android

Android中運用訪問者模式,其實主要是在編譯期註解中,編譯期註解核心原理依賴APT(Annotation Processing Tools),著名的開源庫比如ButterKnife、Dagger、Retrofit都是基於APT。

重構

如果是一些經常需要變動邏輯的業務則非常適合使用訪問者模式,如果是需要頻繁增加新的業務的,則不適合,所以Android的UI展示部分其實理論上來說是適合使用訪問者模式的,因為UI常常一個版本一個變化,如果當UI的變化不只是侷限在XML中修修改改的話,而是已經體現在了程式碼中,那麼可以考慮是否可以使用訪問者模式進行修改。
目前實際專案中暫無這種情況,廣告模組後續的UI渲染由於是根據不同的廣告平臺所下發的廣告物件來進行對應的渲染,目前每個廣告平臺渲染檢視的介面所需引數不太一樣,但是可以考慮進行一下抽離封裝,做成一個簡單的Visitor試試看。

中介者模式

定義

中介者模式包裝了一系列物件相互作用的方式,使得這些物件不必相互明顯呼叫,從而使他們可以輕鬆耦合。當某些物件之間的作用發生改變時,不會立即影響其他的一些物件之間的作用保證這些作用可以彼此獨立的變化,中介者模式將多對多的相互作用轉為一對多的相互作用。
其實,中介者物件是將系統從網狀結構轉為以調停者為中心的星型結構。
舉個簡單的例子,一臺電腦包括:CPU、記憶體、顯示卡、IO裝置。其實,要啟動一臺計算機,有了CPU和記憶體就夠了。當然,如果你需要連線顯示器顯示畫面,那就得加顯示卡,如果你需要儲存資料,那就要IO裝置,但是這並不是最重要的,它們只是分割開來的普通零件而已,我們需要一樣東西把這些零件整合起來,變成一個完整體,這個東西就是主機板。主機板就是起到中介者的作用,任何兩個模組之間的通訊都會經過主機板協調。

示例

public abstract class Person {
    protected String name;
    protected Mediator mediator;

    Person(String name,Mediator mediator){
        this.name = name;
        this.mediator = mediator;
    }

}

public class HouseOwner extends Person{

    HouseOwner(String name, Mediator mediator) {
        super(name, mediator);
    }

    /**
     * @desc 與中介者聯絡
     * @param message
     * @return void
     */
    public void constact(String message){
        mediator.constact(message, this);
    }

    /**
     * @desc 獲取資訊
     * @param message
     * @return void
     */
    public void getMessage(String message){
        System.out.println("房主:" + name +",獲得資訊:" + message);
    }
}

public class Tenant extends Person{

    Tenant(String name, Mediator mediator) {
        super(name, mediator);
    }

    /**
     * @desc 與中介者聯絡
     * @param message
     * @return void
     */
    public void constact(String message){
        mediator.constact(message, this);
    }

    /**
     * @desc 獲取資訊
     * @param message
     * @return void
     */
    public void getMessage(String message){
        System.out.println("租房者:" + name +",獲得資訊:" + message);
    }
}

public abstract class Mediator {
    //申明一個聯絡方法
    public abstract void constact(String message,Person person);
}

public class MediatorStructure extends Mediator{
    //首先中介結構必須知道所有房主和租房者的資訊
    private HouseOwner houseOwner;
    private Tenant tenant;

    public HouseOwner getHouseOwner() {
        return houseOwner;
    }

    public void setHouseOwner(HouseOwner houseOwner) {
        this.houseOwner = houseOwner;
    }

    public Tenant getTenant() {
        return tenant;
    }

    public void setTenant(Tenant tenant) {
        this.tenant = tenant;
    }

    public void constact(String message, Person person) {
        if(person == houseOwner){          //如果是房主,則租房者獲得資訊
            tenant.getMessage(message);
        }
        else{       //反正則是房主獲得資訊
            houseOwner.getMessage(message);
        }
    }
}

public class Client {
    public static void main(String[] args) {
        //一個房主、一個租房者、一箇中介機構
        MediatorStructure mediator = new MediatorStructure();

        //房主和租房者只需要知道中介機構即可
        HouseOwner houseOwner = new HouseOwner("張三", mediator);
        Tenant tenant = new Tenant("李四", mediator);

        //中介結構要知道房主和租房者
        mediator.setHouseOwner(houseOwner);
        mediator.setTenant(tenant);

        tenant.constact("聽說你那裡有三室的房主出租.....");
        houseOwner.constact("是的!請問你需要租嗎?");
    }
}

房主:張三,獲得資訊:聽說你那裡有三室的房主出租…..
租房者:李四,獲得資訊:是的!請問你需要租嗎?

Android

在Binder機制中,就用到了中介者模式。我們知道系統啟動時,各種系統服務會向ServiceManager提交註冊,即ServiceManager持有各種系統服務的引用 ,當我們需要獲取系統的Service時,比如ActivityManager、WindowManager等(它們都是Binder),首先是向ServiceManager查詢指定標示符對應的Binder,再由ServiceManager返回Binder的引用。並且客戶端和服務端之間的通訊是通過Binder驅動來實現,這裡的ServiceManager和Binder驅動就是中介者。

重構

從年初開始就有在專案裡面做MVP的重構,MVP架構裡面P層其實就是一箇中介者,負責協調V和M

外觀模式/門面模式

定義

要求一個子系統的外部與其內部的通訊必須通過一個統一的物件進行。
舉個例子,我們在啟動計算機時,只需按一下開關鍵,無需關係裡面的磁碟、記憶體、cpu、電源等等這些如何工作,我們只關心他們幫我啟動好了就行。實際上,由於裡面的線路太複雜,我們也沒辦法去具體瞭解內部電路如何工作。主機提供唯一一個介面“開關鍵”給使用者就好。

示例

/**
 * cpu子系統類
 */
public class CPU
{
    public static final Logger LOGGER = Logger.getLogger(CPU.class);
    public void start()
    {
        LOGGER.info("cpu is start...");
    }

    public void shutDown()
    {
        LOGGER.info("CPU is shutDown...");
    }
}

/**
 * Disk子系統類
 */
public class Disk
{
    public static final Logger LOGGER = Logger.getLogger(Disk.class);
    public void start()
    {
        LOGGER.info("Disk is start...");
    }

    public void shutDown()
    {
        LOGGER.info("Disk is shutDown...");
    }
}

/**
 * Memory子系統類
 */
public class Memory
{
    public static final Logger LOGGER = Logger.getLogger(Memory.class);
    public void start()
    {
        LOGGER.info("Memory is start...");
    }

    public void shutDown()
    {
        LOGGER.info("Memory is shutDown...");
    }
}

/**
 * 門面類(核心)
 */
public class Computer
{
    public static final Logger LOGGER = Logger.getLogger(Computer.class);

    private CPU cpu;
    private Memory memory;
    private Disk disk;
    public Computer()
    {
        cpu = new CPU();
        memory = new Memory();
        disk = new Disk();
    }
    public void start()
    {
        LOGGER.info("Computer start begin");
        cpu.start();
        disk.start();
        memory.start();
        LOGGER.info("Computer start end");
    }

    public void shutDown()
    {
        LOGGER.info("Computer shutDown begin");
        cpu.shutDown();
        disk.shutDown();
        memory.shutDown();
        LOGGER.info("Computer shutDown end...");
    }
}

/**
 * 客戶端類
 */
public class Cilent {
    public static final Logger LOGGER = Logger.getLogger(Cilent.class);
    public static void main(String[] args)
    {
        Computer computer = new Computer();
        computer.start();
        LOGGER.info("=================");
        computer.shutDown();
    }
}

從上面的例項來看,有了這個Facade類,也就是Computer類,使用者就不用親自去呼叫子系統中的Disk,Memory、CPU類了,不需要知道系統內部的實現細節,甚至都不用知道系統內部的構成。客戶端只需要跟Facade互動就可以了。

Android

那麼Android哪裡使用到了外觀模式呢?依然回到Context,Android內部有很多複雜的功能比如startActivty、sendBroadcast、bindService等等,這些功能內部的實現非常複雜,如果你看了原始碼你就能感受得到,但是我們無需關心它內部實現了什麼,我們只關心它幫我們啟動Activity,幫我們傳送了一條廣播,繫結了Activity等等就夠了。

重構

暫無

代理模式

定義

給某一個物件提供一個代理,並由代理物件控制對原物件的引用。
代理模式有幾種,虛擬代理,計數代理,遠端代理,動態代理。主要分為兩類,靜態代理和動態代理。

靜態代理

定義

靜態代理比較簡單,是由程式設計師編寫的代理類,並在程式執行前就編譯好的,而不是由程式動態產生代理類,這就是所謂的靜態。可以通過聚合和繼承兩種方式實現,繼承方式不夠靈活,所以只介紹聚合的方式

示例

nterface Subject {
    void request();
}

class RealSubject implements Subject {
    public void request(){
        System.out.println("RealSubject");
    }
}

class Proxy implements Subject {
    private Subject subject;

    public Proxy(Subject subject){
        this.subject = subject;
    }
    public void request(){
        System.out.println("begin");
        subject.request();
        System.out.println("end");
    }
}

public class ProxyTest {
    public static void main(String args[]) {
        RealSubject subject = new RealSubject();
        Proxy p = new Proxy(subject);
        p.request();
    }
}

重構

目前專案中有需要載入圖片的業務需求,載入圖片的框架可以有ImageLoader、Glide等等,可以通過介面卡模式讓這些第三方或者自己內部的工具類整合在一起,然後通過靜態代理的方式提供給外部使用圖片處理的相關功能。

動態代理

定義

動態代理中,代理類並不是在Java程式碼中實現,而是在執行時期生成,相比靜態代理,動態代理可以很方便的對委託類的方法進行統一處理,如新增方法呼叫次數、新增日誌功能等等,動態代理分為jdk動態代理和cglib動態代理,下面通過一個例子看看如何實現jdk動態代理。

JDK動態代理示例

//定義業務邏輯
public interface Service {  
    //目標方法 
    public abstract void add();  
} 

public class UserServiceImpl implements Service {  
    public void add() {  
        System.out.println("This is add service");  
    }  
}

//利用java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler介面定義代理類的實現。
class MyInvocatioHandler implements InvocationHandler {
    private Object target;

    public MyInvocatioHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-----before-----");
        Object result = method.invoke(target, args);
        System.out.println("-----end-----");
        return result;
    }
    // 生成代理物件
    public Object getProxy() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class[] interfaces = target.getClass().getInterfaces();
        return Proxy.newProxyInstance(loader, interfaces, this);
    }
}

//使用動態代理
public class ProxyTest {
    public static void main(String[] args) {
        Service service = new UserServiceImpl();
        MyInvocatioHandler handler = new MyInvocatioHandler(service);
        Service serviceProxy = (Service)handler.getProxy();
        serviceProxy.add();
    }
}
執行結果:

-----before-----
This is add service
-----end-----

cglib動態代理示例

前面分析到,因為Java只允許單繼承,而JDK生成的代理類本身就繼承了Proxy類,因此,使用JDK實現的動態代理不能完成繼承式的動態代理,但是我們可以使用cglib來實現繼承式的動態代理。
大名鼎鼎的spring中就含有cglib動態代理,在此也以Spring中自帶的cglib完成動態代理的實現:

//1.具體主題  
public class Train{  
    public void move(){  
        System.out.println("火車行駛中…");  
    }  
}  
//2.生成代理  
public class CGLibProxy implements MethodInterceptor {  
    private Enhancer enhancer = new Enhancer();  
    public Object getProxy(Class clazz){  
        enhancer.setSuperclass(clazz);  
        enhancer.setCallback(this);  
        return enhancer.create();  
    }  
    /** 
     * 攔截所有目標類方法的呼叫 
     * 引數: 
     * obj目標例項物件 
     *method 目標方法的反射物件 
     * args方法的引數 
     * proxy代理類的例項 
     */  
    public Object intercept(Object obj, Method method, Object[] args,  
            MethodProxy proxy) throws Throwable {  
        //代理類呼叫父類的方法  
        System.out.println("日誌開始");  
        proxy.invokeSuper(obj, args);  
        System.out.println("日誌結束");  
        return null;  
    }  
}  
//3.測試  
public class Test {  
    public static void main(String[] args) {  
        CGLibProxy proxy = new CGLibProxy();  
        Train t = (Train) proxy.getProxy(Train.class);  
        t.move();  
    }  
}

重構

在專案中很多地方需要統計,這部分功能放在哪一層都感覺不合適,可以通過動態代理將簡單的統計抽離出來,動態代理其實就是AOP程式設計思想的一種具體實現

小結

動態代理與靜態代理相比較,最大的好處是介面中宣告的所有方法都被轉移到呼叫處理器一個集中的方法中處理。在介面方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣對每一個方法或方法組合進行處理。Proxy 很美很強大,但是僅支援 interface 代理。Java 的單繼承機制註定了這些動態代理類們無法實現對 class 的動態代理。好在有cglib為Proxy提供了彌補。class與interface的區別本來就模糊,在java8中更是增加了一些新特性,使得interface越來越接近class,當有一日,java突破了單繼承的限制,動態代理將會更加強大。

Android

AIDL會根據當前的執行緒判斷是否要跨程式訪問,如果不需要跨程式就直接返回例項,如果需要跨程式則返回一個代理。而在跨程式通訊時,需要把引數寫入到Parcelable物件,然後再執行transact函式,AIDL通過生成一個代理類,這個代理類會自動幫我們寫好這些操作。
而要實現Android的外掛化開發,動態代理更是必不可少的。

中介者、代理、外觀模式三者的區別

  • 中介者模式:A,B之間的對話通過C來傳達。A,B可以互相不認識(減少了A和B物件間的耦合)
  • 代理模式:A要送B禮物,A,B互相不認識,那麼A可以找C來幫它實現送禮物的願望(封裝了A物件)
  • 外觀模式:A和B都要實現送花,送巧克力的方法,那麼我可以通過一個抽象類C實現送花送巧克力的方法(A和B都繼承C)。(封裝了A,B子類)

代理模式和外觀者模式這兩種模式主要不同就是代理模式針對的是單個物件,而外觀模式針對的是所有子類。

裝飾模式

定義

動態的給一個物件新增額外的職責,就增加功能來說,裝飾模式比子類繼承的方式更靈活。

我們通常可以使用繼承來實現功能的擴充,如果這些需要擴充的功能的種類很繁多,那麼勢必生成很多子類,增加系統的複雜性,同時,使用繼承實現功能擴充,我們必須可預見這些擴充功能,這些功能是編譯時就確定了,是靜態的。

使用Decorator的理由是:這些功能需要由使用者動態決定加入的方式和時機。Decorator提供了”即插即用”的方法,在執行期間決定何時增加何種功能。

示例

public abstract class Component{
    public abstract void operate();
}

public class ConcreteComponent extends Component{
    public void operate(){
        //具體的實現
    }
}

public class Decorator extends Component {
    private Component component;
    public Decorator(Component component){
        this.component = component;
    }

    public void operate(){
        operateA();
        component.operate();
        operateB();
    }
    public void operateA(){
        //具體操作
    }
    public void operateB(){
        //具體操作
    }
}

public static void main(String[] args) { 
        // 使用普通功能類 
        Component concreteComponent = new ConcreteComponent();
        Component decorator = new Decorator(concreteComponent);
        decorator.operate();
    } 
}

如果你細心,會發現,上面呼叫類似我們讀取檔案時的呼叫:

FileReader fr = new FileReader(filename);
BufferedReader br = new BufferedReader(fr);

實際上Java 的I/O API就是使用Decorator實現的,I/O變種很多,如果都採取繼承方法,將會產生很多子類,顯然相當繁瑣。

Android

那麼在Android哪裡出現了裝飾模式呢?我們平時經常用到Context類,但是其實Context類只是個抽象類,具體實現是ContextImpl,那麼誰是ContextImpl的裝飾類呢?我們知道Activity是個Context,但是Activity 並不是繼承於Context,而是繼承於ContextThremeWrapper.而ContextThremeWrapper繼承於ContextWrapper,ContextWrapper繼承Context.說了這麼多,跟裝飾模式有啥關係?主要是引入ContextWrapper這個類。ContextWrapper內部有個Context引用mContext,並且ContextWrapper中對Context的每個方法都有實現,在實現中呼叫的就是mContext相同的方法。

重構

子類過多的時候可以考慮使用

裝飾模式與代理模式的區別

裝飾模式:以對客戶端透明的方式擴充套件物件的功能,是繼承關係的一個替代方案;
代理模式:給一個物件提供一個代理物件,並有代理物件來控制對原有物件的引用;
裝飾模式應該為所裝飾的物件增強功能;代理模式對代理的物件施加控制,並不提供物件本身的增強功能
你在一個地方寫裝飾,大家就知道這是在增加功能,你寫代理,大家就知道是在限制,

組合模式

定義

將物件組成成樹形結構,以表示“部分-整體”的層次結構,使得使用者對單個物件和組合物件的使用具有一致性。

示例

public class Employee {
   private String name;
   private String dept;
   private int salary;
   private List subordinates;

   //建構函式
   public Employee(String name,String dept, int sal) {
      this.name = name;
      this.dept = dept;
      this.salary = sal;
      subordinates = new ArrayList();
   }

   public void add(Employee e) {
      subordinates.add(e);
   }

   public void remove(Employee e) {
      subordinates.remove(e);
   }

   public List getSubordinates(){
     return subordinates;
   }

   public String toString(){
      return ("Employee :[ Name : "+ name
      +", dept : "+ dept + ", salary :"
      + salary+" ]");
   }   
}

public class CompositePatternDemo {
   public static void main(String[] args) {
      Employee CEO = new Employee("John","CEO", 30000);

      Employee headSales = new Employee("Robert","Head Sales", 20000);

      Employee headMarketing = new Employee("Michel","Head Marketing", 20000);

      Employee clerk1 = new Employee("Laura","Marketing", 10000);
      Employee clerk2 = new Employee("Bob","Marketing", 10000);

      Employee salesExecutive1 = new Employee("Richard","Sales", 10000);
      Employee salesExecutive2 = new Employee("Rob","Sales", 10000);

      CEO.add(headSales);
      CEO.add(headMarketing);

      headSales.add(salesExecutive1);
      headSales.add(salesExecutive2);

      headMarketing.add(clerk1);
      headMarketing.add(clerk2);

      //列印該組織的所有員工
      System.out.println(CEO);
      for (Employee headEmployee : CEO.getSubordinates()) {
         System.out.println(headEmployee);
         for (Employee employee : headEmployee.getSubordinates()) {
            System.out.println(employee);
         }
      }       
   }
}

Android

Android中View的結構是樹形結構,每個ViewGroup包含一系列的View,而ViewGroup本身又是View。這是Android中非常典型的組合模式。

重構

暫無

介面卡模式

定義

把一個類的介面變換成客戶端所期待的另一個介面,從而使原本因介面不匹配而無法在一起工作的兩個類能夠在一起工作。

示例

// 目標介面,或稱為標準介面,即客戶所期待的介面
interface Target { 
    public void request(); 
} 

// 具體目標類,只提供普通功能 
class ConcreteTarget implements Target { 
    public void request() { 
        System.out.println("普通類 具有 普通功能..."); 
    } 
} 

// 已存在的、具有特殊功能、但不符合我們既有的標準介面的類,也是需要被適配的類
class Adaptee { 
    public void specificRequest() { 
        System.out.println("被適配類具有特殊功能..."); 
    } 
}   

// 介面卡類,繼承了被適配類,同時實現標準介面 
class Adapter extends Adaptee implements Target{ 
    public void request() { 
        super.specificRequest(); 
    } 
} 

// 測試類public class Client { 
public static void main(String[] args) { 
        // 使用普通功能類 
        Target concreteTarget = new ConcreteTarget(); 
        concreteTarget.request(); 

        // 使用特殊功能類,即適配類 
        Target adapter = new Adapter(); 
        adapter.request(); 
    } 
}

Android

比較典型的有ListView和RecyclerView。為什麼ListView需要使用介面卡呢?ListView用於顯示列表資料,但列表資料形式多種多樣,為了處理和顯示不同的資料,我們需要對應的介面卡作為橋樑。這樣ListView就可以只關心它的每個ItemView,而不用關心這個ItemView具體顯示的是什麼。而我們的資料來源存放的是要顯示的內容,它儲存了每一個ItemView要顯示的內容。ListView和資料來源之間沒有任何關係,這時候,需要通過介面卡,介面卡提供getView方法給ListView使用,每次ListView只需提供位置資訊給getView函式,然後getView函式根據位置資訊向資料來源獲取對應的資料,根據資料返回不同的View。

重構

當想使用一個既有類的介面,但是這個既有類與目前的程式碼結構不相相容的時候可以考慮使用介面卡模式。

享元模式

定義

享元模式的英文是Flyweight,在拳擊比賽中指最輕量級,即“蠅量級”或“雨量級”,這裡選擇使用“享元模式”的意譯,是因為這樣更能反映模式的用意。享元模式是物件的結構模式。享元模式以共享的方式高效地支援大量的細粒度物件。

享元模式還分單純享元模式和複合享元模式

示例

在JAVA語言中,String型別就是使用了享元模式。String物件是final型別,物件一旦建立就不可改變。在JAVA中字串常量都是存在常量池中的,JAVA會確保一個字串常量在常量池中只有一個拷貝。String a=”abc”,其中”abc”就是一個字串常量。

Android

享元模式我們平時接觸真的很多,比如Java中的常量池,執行緒池等。主要是為了重用物件。

在Android哪裡用到了享元模式呢?執行緒通訊中的Message,每次我們獲取Message時呼叫Message.obtain()其實就是從訊息池中取出可重複使用的訊息,避免產生大量的Message物件。

重構

暫無

橋接模式

定義

將抽象部分與實現部分分離,使他們獨立地進行變化。
其實就是,一個類存在兩個維度的變化,且這兩個維度都需要進行擴充套件。

拿汽車在路上行駛的來說。既有小汽車又有公共汽車,它們都不但能在市區中的公路上行駛,也能在高速公路上行駛。這你會發現,對於交通工具(汽車)有不同的型別,它們所行駛的環境(路)也有不同型別,在軟體系統中就要適應兩個方面(不同車型,不同道路)的變化,怎樣實現才能應對這種變化呢?

在軟體系統中,某些型別由於自身的邏輯,它具有兩個或多個維度的變化,那麼如何應對這種“多維度的變化”?如何利用面嚮物件的技術來使得該型別能夠輕鬆的沿著多個方向進行變化,而又不引入額外的複雜度?這就要使用Bridge模式。Bridge模式是一個非常有用的模式,也非常複雜,它很好的符合了開放-封閉原則和優先使用物件,而不是繼承這兩個物件導向原則。

示例

首先看一下不應用橋接模式時的上述汽車例子程式碼:

//基類 路 
class Road { 
    void run() { 
        System.out.println("路"); 
    } 
} 
//市區街道 
class Street extends Road { 
    void run() { 
        System.out.println("市區街道"); 
    } 
} 
//高速公路 
class SpeedWay extends Road { 
    void run() { 
        System.out.println("高速公路"); 
    } 
} 
//小汽車在市區街道行駛 
class CarOnStreet extends Street { 
    void run() { 
        System.out.println("小汽車在市區街道行駛"); 
    } 
} 
//小汽車在高速公路行駛 
class CarOnSpeedWay extends SpeedWay { 
    void run() { 
        System.out.println("小汽車在高速公路行駛"); 
    } 
} 
//公交車在市區街道行駛 
class BusOnStreet extends Street { 
    void run() { 
        System.out.println("公交車在市區街道行駛"); 
    } 
} 
//公交車在高速公路行駛 
class BusOnSpeedWay extends SpeedWay { 
    void run() { 
        System.out.println("公交車在高速公路行駛"); 
    } 
} 
//測試 
public static void main(String[] args) { 

    //小汽車在高速公路行駛 
    CarOnSpeedWay carOnSpeedWay = new CarOnSpeedWay(); 
    carOnSpeedWay.run(); 
    //公交車在市區街道行駛 
    BusOnStreet busOnStreet = new BusOnStreet(); 
    busOnStreet.run(); 

}

但是我們說這樣的設計是脆弱的,仔細分析就可以發現,它還是存在很多問題,首先它在遵循開放-封閉原則的同時,違背了類的單一職責原則,即一個類只有一個引起它變化的原因,而這裡引起變化的原因卻有兩個,即路型別的變化和汽車型別的變化;其次是重複程式碼會很多,不同的汽車在不同的路上行駛也會有一部分的程式碼是相同的;

再次是類的結構過於複雜,繼承關係太多,難於維護,最後最致命的一點是擴充套件性太差。如果變化沿著汽車的型別和不同的道路兩個方向變化,我們會看到這個類的結構會迅速的變龐大。
接下來我們再看一下應用了橋接模式之後的程式碼:

abstract class AbstractRoad{ 
    AbstractCar aCar; 
    void run(){}; 
} 
abstract class AbstractCar{ 
    void run(){}; 
} 

class Street extends AbstractRoad{ 
    @Override 
    void run() { 
        // TODO Auto-generated method stub 
        super.run(); 
        aCar.run(); 
        System.out.println("在市區街道行駛"); 
    } 
} 
class SpeedWay extends AbstractRoad{ 
    @Override 
    void run() { 
        // TODO Auto-generated method stub 
        super.run(); 
        aCar.run(); 
        System.out.println("在高速公路行駛"); 
    } 
} 
class Car extends AbstractCar{ 
    @Override 
    void run() { 
        // TODO Auto-generated method stub 
        super.run(); 
        System.out.print("小汽車"); 
    } 
} 
class Bus extends AbstractCar{ 
    @Override 
    void run() { 
        // TODO Auto-generated method stub 
        super.run(); 
        System.out.print("公交車"); 
    } 
} 

public static void main(String[] args){ 

    AbstractRoad speedWay = new SpeedWay(); 
    speedWay.aCar = new Car(); 
    speedWay.run(); 

    AbstractRoad street = new Street(); 
    street.aCar = new Bus(); 
    street.run(); 
}

可以看到,通過物件組合的方式,Bridge 模式把兩個角色之間的繼承關係改為了耦合的關係,從而使這兩者可以從容自若的各自獨立的變化,這也是Bridge模式的本意。
這樣增加了客戶程式與路與汽車的耦合。其實這樣的擔心是沒有必要的,因為這種耦合性是由於物件的建立所帶來的,完全可以用建立型模式去解決。在應用時結合建立型設計模式來處理具體的問題。

Android

在Android中橋接模式用的很多,舉個例子,對於一個View來說,它有兩個維度的變化,一個是它的描述比如Button、TextView等等他們是View的描述維度上的變化,另一個維度就是將View真正繪製到螢幕上,這跟Display、HardwareLayer和Canvas有關。這兩個維度可以看成是橋接模式的應用。

重構

暫無

感悟

本文是在看完《重構:改善既有程式碼之道》,開始看四人幫的《設計模式:可複用物件導向軟體的基礎》之前,結合多篇博文、實際專案與個人理解寫下的。

推薦一定要在有所積累甚至遇到一些你已經沒必要用以往經驗解決的問題(這樣的解決是指最佳方案,而不是隻求解決就好)的時候去看這兩個思想,共鳴真的很重要。

而對於這種體系龐雜的技術知識的學習,我一向不建議只看幾篇博文就了事,這樣大多隻會留於表面,除了拒絕紙上談兵多實踐之外,很多最重要的內容往往在書裡才能說得詳盡,就像各個模式之間怎麼協調,各個模式之間有什麼區別,多種多樣的使用場景,在一個模式就能寫一篇論文的情況下,幾篇博文怎麼可能學得盡。

最早看設計模式是在兩年半前,那時候還沒畢業,看的是《大話設計模式》,那時候沒有多少積累,對架構最說得上的就一個MVC,所以即使看的是以通俗著稱的《大話》還是雲裡霧裡,看沒兩章就放棄了。

但是在程式碼量不斷的積累和對問題不斷的發現之後,再重新去看《重構》與《設計模式》,這時候的感覺是完全不一樣的,特別在我之前做了一段時間專案的重構之後,不誇張地說,在看《重構》這本書時,每一章都有那種讓我相見恨晚的感覺。

技術方面的成長是多方面的,一方面在於經驗的積累,但是經驗的積累受限於很多現實因素,另一方面的成長則是思想上的進化,當然這也可以通過實際做專案來提升,但是同樣的,通過專案來學習總有一天會到瓶頸,這時候就代表你該去收穫那些巨人們留下的思想了。

之前有在一些地方看過重構無用、設計模式無用之類的理論,其實並不是重構與設計模式無用,而是自己還沒有到能覺得它們有用的境界,設計模式絕不是偶爾掛在嘴邊聊聊兩句定義就好的談資,作為java程式設計師必讀的三本書之一的《設計模式》,能夠這麼長時間傳承下來自有其底蘊所在。

最後吐槽一下,《設計模式》這本書真的是神書,可是真的也晦澀難懂,很大一部分原因在於四人幫這四位大神對設計模式的深度理解而造成寫書時極其簡練抽象的表述,所以在堅持完前面兩章後再次宣告放棄,轉身投向《Head First:設計模式》的懷抱,前者200多頁,後者700多頁,但是哪本好讀些大家都懂。

不過重構與設計模式本身就都不是能一蹴即就的,這兩者都是需要不斷地應用、重新認識、再應用,這樣一直重複之後才能完全融會貫通,所以也不必糾結於今天看完就要全部掌握,每隔一段時間再看一下,遇到問題的時候回過頭來向其尋找解決方案,這才是學習這兩大思想的正途。

把《重構》《設計模式》這類更偏向于思想層面的知識放到年初就學也是為了之後進一步學習《Effective Java》、《Java程式設計思想》與Android原始碼做鋪墊,閱讀原始碼最佳的方法不是從第一行開始往下讀,要麼是通過問題出發,以點及面;要麼就是你先試圖從編碼者的思想層面出發,有時候當你發現了這個模組的設計思路與模式之後,很多東西都是一點就通的,你甚至知識看到一個模組的類結構就知道整個模組的大體內容,然後就像是你自己寫的這些程式碼一樣,找任何東西都輕而易舉。就像如果你不懂泛型,即使寫過四五個大專案,看到一堆用上泛型的第三方庫的原始碼一樣懵逼。

參考

相關文章