Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android資訊 >> 談談 23 種設計模式在 Android 項目中的應用

談談 23 種設計模式在 Android 項目中的應用

編輯:Android資訊

前言

本文將結合實際談談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源碼做鋪墊,閱讀源碼最佳的方法不是從第一行開始往下讀,要麼是通過問題出發,以點及面;要麼就是你先試圖從編碼者的思想層面出發,有時候當你發現了這個模塊的設計思路與模式之後,很多東西都是一點就通的,你甚至知識看到一個模塊的類結構就知道整個模塊的大體內容,然後就像是你自己寫的這些代碼一樣,找任何東西都輕而易舉。就像如果你不懂泛型,即使寫過四五個大項目,看到一堆用上泛型的第三方庫的源碼一樣懵逼。

參考

  • 從Android代碼中來記憶23種設計模式
  • Android 源碼中的工廠方法模式
  • Android設計模式(十二)--抽象工廠模式
  • Java設計模式二:抽象工廠模式(Abstract Factory)/工廠方法(Factory Method)
  • Android源碼中的命令模式
  • Java設計模式系列之迭代器模式
  • 《JAVA與模式》之訪問者模式
  • 設計模式讀書筆記—–中介者模式
  • java設計模式之外觀模式(門面模式)
  • 組合模式
  • JAVA設計模式初探之適配器模式
  • Java設計模式(7)裝飾模式(Decorator模式)
  • 《JAVA與模式》之享元模式
  • JAVA設計模式初探之橋接模式
  • 說說Java代理模式
  • Java 代理模式和裝飾者模式的區別
  • Java設計模式——代理模式實現及原理
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved