Android設計模式——策略模式之原始碼使用場景(三)

1008711發表於2019-02-27

一、前言

策略模式可以定義一系列的演算法,並將每一個演算法封裝起來,而且使它們還可以相互替換。策略模式讓演算法獨立於使用它的客戶而獨立變化。

二、策略模式的使用場景

  • 針對同一型別問題的多種處理方式,僅僅是具體行為有差別時。
  • 需要安全地封裝多種同一型別的操作時。
  • 出現同一個抽象類有多個子類,而又需要使用if-else或者switch-case來選擇具體子類時。

三、策略模式的實現

通常一個問題有多個解決方案時,最簡單的方式就是利用if-else或者switch-case方式根據不同的情景選擇不同的解決方案,但這種簡單的方案問題太多,例如耦合性太高、程式碼臃腫、難維護等。但是如果解決方案中包括大量的處理邏輯需要封裝,或者處理方式變動較大的時候則就顯得混亂、複雜,當需要增加一種方案時就需要修改類中的程式碼。

使用if-else這種方法確實不會遵循開閉原則,而應對這種情況策略模式就能很好地解決這類問題,它將各種方案分離開來,讓程式客戶端根據具體的需求來動態地選擇不同的策略方案。

下面我們通過交通工具來進行費用計算演示,對於部分城市的公交都是實行分時段計價,坐得越遠費用越高。顯然公交與地鐵的價格計算是不一樣的,我們算一下乘坐不同出行工具的成本,程式碼:

public class PriceCalculator {

    private static final int BUS = 1;
    private static final int SUBWAY = 2;

    public static void main(String[] strings) {
        PriceCalculator calculator = new PriceCalculator();
        System.out.println("做20km公交票價:" + calculator.calculatePrice(20, BUS));
        System.out.println("做20km地鐵票價:" + calculator.calculatePrice(20, SUBWAY));

    }


    /**
     * 公交車計價    10公里之內1塊錢,超過10公里每加一塊可以乘5公里
     *
     * @param km
     * @return
     */
    private int busPrice(int km) {
        //超過10公里的距離
        int extraTotal = km - 10;
        //超過距離是5km的倍數
        int extraFactor = extraTotal / 5;
        //超過的距離對5km取餘
        int fraction = extraTotal % 5;
        //價格計算
        int price = 1 + extraFactor % 5;
        return fraction > 0 ? ++price : price;
    }

    /**
     * 6km內3塊,6—12km是4塊,12—22km是5塊,22-32km是6塊,其他距離7塊
     *
     * @param km
     * @return
     */
    public int subwayPrice(int km) {
        if (km <= 6) {
            return 3;
        } else if (km > 6 && km < 12) {
            return 4;
        } else if (km > 12 && km < 22) {
            return 5;
        } else if (km > 22 && km < 32) {
            return 6;
        }
        return 7;
    }

    /**
     * 根據不同型別計算
     *
     * @param km
     * @param type
     * @return
     */
    int calculatePrice(int km, int type) {
        if (type == BUS) {
            return busPrice(km);
        } else if (type == SUBWAY) {
            return subwayPrice(km);
        }
        return 0;
    }

}複製程式碼

PriceCalculator 類很明顯的問題就是並不是單一職責,首先它承擔計算公交車和地鐵乘坐價格的職責,另一個問題就是通過if-else的形式來判斷使用哪種計算形式。當我們增加一種出行方式時,如計程車,那麼我們就需要在PriceCalculator 中增加一個方法來計算計程車出行的價格,並且在calculatePrice(int km, int type)函式增加一個判斷,程式碼:

public class PriceCalculator {

    
    private static final int TAXI = 3;

    /***
     * 每公里2塊
     *
     * @param km
     * @return
     */
    private int taixPrice(int km) {
        return km * 2;
    }

    /**
     * 根據不同型別計算
     *
     * @param km
     * @param type
     * @return
     */
    int calculatePrice(int km, int type) {
        if (type == BUS) {
            return busPrice(km);
        } else if (type == SUBWAY) {
            return subwayPrice(km);
        } else if (type == TAXI) {
            return taixPrice(km);
        }
        return 0;
    }

}複製程式碼

此時程式碼已經很混亂,各種if-else語句纏繞在一起。當價格的計算方法變化時,需要直接修改這個類中的程式碼,那麼很可能有一段程式碼是其他幾個計算方法所公共使用的,這就容易引入錯誤。另外增加出行方式時,我們又需要在calculatePrice中新增if-else,此時很有可能就是複製一個if-else,然後手動進行修改,手動複製程式碼也是最容易引入錯誤的做法之一。這類程式碼必然是難以應對變化的,它會使得程式碼變得越來越臃腫,難以維護,我們解決這類問題的手法也就是使用策略模式。我們也可以把每種計算方式獨立成一個函式,然後外部呼叫對應的方法即可,但這也是另一種耦合的形式,對於可變性較大的演算法來說還是不適合使用這種方式。

接下來使用策略模式對以上進行重構。首先定義一個抽象的價格計算介面:

//計算介面
public interface CalculateStrategy {
    //按距離來計算價格
    int calculatePrice(int km);
}複製程式碼

對於每一種出行方式我們都有一個獨立的計算策略類,這些策略類都實現了CalculateStrategy 介面,下面是公交車與地鐵的計算策略類:

public class BusStrategy implements CalculateStrategy {
    @Override
    public int calculatePrice(int km) {
        int extraTotal = km - 10;
        int extraFactor = extraTotal / 5;
        int fraction = extraTotal % 5;
        int price = 1 + extraTotal % 5;
        return fraction > 0 ? ++price : price;
    }
}複製程式碼
public class SubwayStrategy implements CalculateStrategy {
    @Override
    public int calculatePrice(int km) {
        if (km <= 6) {
            return 3;
        } else if (km > 6 && km < 12) {
            return 4;
        } else if (km > 12 && km < 22) {
            return 5;
        } else if (km > 22 && km < 32) {
            return 6;
        }
        return 7;
    }
}複製程式碼

我們再建立一個扮演Context角色的類,這裡將命名為TranficCalculator,程式碼:

public class TranficCalculator {

    private CalculateStrategy strategy;

    public void setStrategy(CalculateStrategy strategy) {
        this.strategy = strategy;
    }

    public int calclatePrice(int km) {
        return strategy.calculatePrice(km);
    }

    public static void main(String[] strings) {
        TranficCalculator calculator = new TranficCalculator();
        calculator.setStrategy(new BusStrategy());
        System.out.println("公交車20km價格:" + calculator.calclatePrice(20));
    }

}複製程式碼

這種方式在隱藏實現的同時,可擴充套件性變得很強,當我們需要新增計程車的計算策略時,只需要新增一個計程車計算策略類,然後將該策略設定給TranficCalculator ,最好直接通過TranficCalculator 物件的計算方法即可。程式碼:

public class TaxiStratrategy implements CalculateStrategy {
    @Override
    public int calculatePrice(int km) {
        return km * 2;
    }
}複製程式碼

將策略注入TranficCalculator中。

public class TranficCalculator {

    private CalculateStrategy strategy;

    public void setStrategy(CalculateStrategy strategy) {
        this.strategy = strategy;
    }

    public int calclatePrice(int km) {
        return strategy.calculatePrice(km);
    }

    public static void main(String[] strings) {
        TranficCalculator calculator = new TranficCalculator();
        calculator.setStrategy(new TaxiStratrategy());
        System.out.println("計程車20km價格:" + calculator.calclatePrice(20));
    }

}複製程式碼

通過以上清晰的看出二者的區別。前者通過if-else來解決問題,實現簡單,層級單一,暴露問題多,程式碼臃腫,邏輯複雜,難以升級與維護,沒有結構可言;後者通過建立抽象,不同的策略建成一個具體的策略實現,通過不同的策略實現演算法替換。簡化邏輯、結構的同時,增強系統的可讀性、穩定性、可擴充套件性,對於較複雜的邏輯顯得更為直觀,擴充套件更為方便。

四、Android原始碼中使用的策略模式

在動畫執行過程中,我們需要一些動態效果,有點類似電影的慢鏡頭,有時需要慢一點,有時需要快一點,這樣動畫變得靈動起來,這些動態效果就是通過插值器(TimeInterpolator)實現的,我們只需要對Animation物件設定不同的插值器就可以實現不同的動態效果,該效果則使用的就是策略模式實現。

  • AccelerateDecelerateInterpolator 在動畫開始與介紹的地方速率改變比較慢,在中間的時候加速
  • AccelerateInterpolator 在動畫開始的地方速率改變比較慢,然後開始加速
  • AnticipateInterpolator 開始的時候向後然後向前甩
  • AnticipateOvershootInterpolator 開始的時候向後然後向前甩一定值後返回最後的值
  • BounceInterpolator 動畫結束的時候彈起
  • CycleInterpolator 動畫迴圈播放特定的次數,速率改變沿著正弦曲線
  • DecelerateInterpolator 在動畫開始的地方快然後慢
  • LinearInterpolator 以常量速率改變
  • OvershootInterpolator 向前甩一定值後再回到原來位置
  • PathInterpolator 路徑插值器

相關文章