Android常見設計模式九:策略模式

對于開發(fā)人員來說捏题,設計模式有時候就是一道坎髓需,但是設計模式又非常有用许师,過了這道坎,它可以讓你水平提高一個檔次僚匆。而在android開發(fā)中微渠,必要的了解一些設計模式又是必須的,因為設計模式在Android源碼中咧擂,可以說是無處不在逞盆。對于想系統(tǒng)的學習設計模式的同學,這里推薦一本書松申,《大話設計模式》云芦。


Android常用設計模式系列:

面向?qū)ο蟮幕A特征
面向?qū)ο蟮脑O計原則
單例模式
模板模式
適配器模式
工廠模式
代理模式
原型模式
策略模式
Build模式
觀察者模式
裝飾者模式
中介模式
門面模式


策略模式

策略模式是非常常見的設計模式之一呐籽,寫個筆記昙读,記錄一下我的學習過程和心得千贯。

首先了解一些策略模式的定義美浦。

策略模式定義了一些列的算法枫绅,并將每一個算法封裝起來属拾,而且使它們還可以相互替換舞萄。策略模式讓算法獨立于使用它的客戶而獨立變換甸祭。

乍一看设联,一如既往的一臉懵逼善已,還是舉個栗子吧灼捂。

假設我們要出去旅游,而去旅游出行的方式有很多换团,有步行悉稠,有坐火車,有坐飛機等等艘包。而如果不使用任何模式的猛,我們的代碼可能就是這樣子的。

    public class TravelStrategy {
        enum Strategy{
            WALK,PLANE,SUBWAY
        }
        private Strategy strategy;
        public TravelStrategy(Strategy strategy){
            this.strategy=strategy;
        }

        public void travel(){
            if(strategy==Strategy.WALK){
                print("walk");
            }else if(strategy==Strategy.PLANE){
                print("plane");
            }else if(strategy==Strategy.SUBWAY){
                print("subway");
            }
        }

        public static void main(String[] args) {
            TravelStrategy walk=new TravelStrategy(Strategy.WALK);
            walk.travel();
            TravelStrategy plane=new TravelStrategy(Strategy.PLANE);
            plane.travel();
            TravelStrategy subway=new TravelStrategy(Strategy.SUBWAY);
            subway.travel();
        }
    }

這樣做有一個致命的缺點想虎,一旦出行的方式要增加卦尊,我們就不得不增加新的else if語句,而這違反了面向?qū)ο蟮脑瓌t之一舌厨,對修改封閉岂却。而這時候,策略模式則可以完美的解決這一切裙椭。

首先躏哩,需要定義一個策略接口。

public interface Strategy {
  void travel();
}

然后根據(jù)不同的出行方式實行對應的接口

    public class WalkStrategy implements Strategy{
        @Override
        public void travel() {
            System.out.println("walk");
        }
    }
    public class PlaneStrategy implements Strategy{
        @Override
        public void travel() {
            System.out.println("plane");
        }
    }
    public class SubwayStrategy implements Strategy{
        @Override
        public void travel() {
            System.out.println("subway");
        }
    }

此外還需要一個包裝策略的類揉燃,并調(diào)用策略接口中的方法

    public class TravelContext {
        Strategy strategy;
        public Strategy getStrategy() {
            return strategy;
        }
        public void setStrategy(Strategy strategy) {
            this.strategy = strategy;
        }
        public void travel() {
            if (strategy != null) {
                strategy.travel();
            }
        }
    }

測試一下代碼

    public class Main {
        public static void main(String[] args) {
            TravelContext travelContext=new TravelContext();
            travelContext.setStrategy(new PlaneStrategy());
            travelContext.travel();
            travelContext.setStrategy(new WalkStrategy());
            travelContext.travel();
            travelContext.setStrategy(new SubwayStrategy());
            travelContext.travel();
        }
    }

輸出結(jié)果如下

plane
walk
subway

可以看到扫尺,應用了策略模式后,如果我們想增加新的出行方式炊汤,完全不必要修改現(xiàn)有的類正驻,我們只需要實現(xiàn)策略接口即可,這就是面向?qū)ο笾械膶U展開放準則抢腐。假設現(xiàn)在我們增加了一種自行車出行的方式姑曙。只需新增一個類即可。

    public class BikeStrategy implements Strategy{
        @Override
        public void travel() {
            System.out.println("bike");
        }
    }

之后設置策略即可

    public class Main {
        public static void main(String[] args) {
            TravelContext travelContext=new TravelContext();
            travelContext.setStrategy(new BikeStrategy());
            travelContext.travel();
        }
    }

廣泛應用

而在Android的系統(tǒng)源碼中,策略模式也是應用的相當廣泛的.最典型的就是屬性動畫中的應用.

想詳細了解屬性動畫的氓栈,可以看我另外一系列文章 android動畫

我們知道,在屬性動畫中,有一個東西叫做插值器,它的作用就是根據(jù)時間流逝的百分比來來計算出當前屬性值改變的百分比.

我們使用屬性動畫的時候,可以通過set方法對插值器進行設置.可以看到內(nèi)部維持了一個時間插值器的引用渣磷,并設置了getter和setter方法,默認情況下是先加速后減速的插值器授瘦,set方法如果傳入的是null醋界,則是線性插值器。而時間插值器TimeInterpolator是個接口提完,有一個接口繼承了該接口形纺,就是Interpolator這個接口,其作用是為了保持兼容

    private static final TimeInterpolator sDefaultInterpolator =
            new AccelerateDecelerateInterpolator();
    private TimeInterpolator mInterpolator = sDefaultInterpolator;
    @Override
    public void setInterpolator(TimeInterpolator value) {
        if (value != null) {
            mInterpolator = value;
        } else {
            mInterpolator = new LinearInterpolator();
        }
    }
    @Override
    public TimeInterpolator getInterpolator() {
        return mInterpolator;
    }
    public interface Interpolator extends TimeInterpolator {
        // A new interface, TimeInterpolator, was introduced for the new android.animation
        // package. This older Interpolator interface extends TimeInterpolator so that users of
        // the new Animator-based animations can use either the old Interpolator implementations or
        // new classes that implement TimeInterpolator directly.
    }

此外還有一個BaseInterpolator插值器實現(xiàn)了Interpolator接口徒欣,并且是一個抽象類

    abstract public class BaseInterpolator implements Interpolator {
        private int mChangingConfiguration;
        /**
         * @hide
         */
        public int getChangingConfiguration() {
            return mChangingConfiguration;
        }
        /**
         * @hide
         */
        void setChangingConfiguration(int changingConfiguration) {
            mChangingConfiguration = changingConfiguration;
        }
    }

平時我們使用的時候逐样,通過設置不同的插值器,實現(xiàn)不同的動畫速率變換效果,比如線性變換脂新,回彈挪捕,自由落體等等。這些都是插值器接口的具體實現(xiàn)争便,也就是具體的插值器策略级零。我們略微來看幾個策略。

    public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
        public LinearInterpolator() {
        }
        public LinearInterpolator(Context context, AttributeSet attrs) {
        }
        public float getInterpolation(float input) {
            return input;
        }
        /** @hide */
        @Override
        public long createNativeInterpolator() {
            return NativeInterpolatorFactoryHelper.createLinearInterpolator();
        }
    }
    public class AccelerateDecelerateInterpolator extends BaseInterpolator
            implements NativeInterpolatorFactory {
        public AccelerateDecelerateInterpolator() {
        }
        @SuppressWarnings({"UnusedDeclaration"})
        public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
        }
        public float getInterpolation(float input) {
            return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
        }
        /** @hide */
        @Override
        public long createNativeInterpolator() {
            return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
        }
    }

內(nèi)部使用的時候直接調(diào)用getInterpolation方法就可以返回對應的值了滞乙,也就是屬性值改變的百分比奏纪。

屬性動畫中另外一個應用策略模式的地方就是估值器,它的作用是根據(jù)當前屬性改變的百分比來計算改變后的屬性值斩启。該屬性和插值器是類似的序调,有幾個默認的實現(xiàn)。其中TypeEvaluator是一個接口兔簇。

public interface TypeEvaluator<T> {
  public T evaluate(float fraction, T startValue, T endValue);
 } 
    public class IntEvaluator implements TypeEvaluator<Integer> {
        public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
            int startInt = startValue;
            return (int)(startInt + fraction * (endValue - startInt));
        }
    }
    public class FloatEvaluator implements TypeEvaluator<Number> {
        public Float evaluate(float fraction, Number startValue, Number endValue) {
            float startFloat = startValue.floatValue();
            return startFloat + fraction * (endValue.floatValue() - startFloat);
        }
    }
    public class PointFEvaluator implements TypeEvaluator<PointF> {
        private PointF mPoint;
        public PointFEvaluator() {
        }
        public PointFEvaluator(PointF reuse) {
            mPoint = reuse;
        }
        @Override
        public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
            float x = startValue.x + (fraction * (endValue.x - startValue.x));
            float y = startValue.y + (fraction * (endValue.y - startValue.y));
            if (mPoint != null) {
                mPoint.set(x, y);
                return mPoint;
            } else {
                return new PointF(x, y);
            }
        }
    }
    public class ArgbEvaluator implements TypeEvaluator {
        private static final ArgbEvaluator sInstance = new ArgbEvaluator();
        public static ArgbEvaluator getInstance() {
            return sInstance;
        }
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            int startInt = (Integer) startValue;
            int startA = (startInt >> 24) & 0xff;
            int startR = (startInt >> 16) & 0xff;
            int startG = (startInt >> 8) & 0xff;
            int startB = startInt & 0xff;
            int endInt = (Integer) endValue;
            int endA = (endInt >> 24) & 0xff;
            int endR = (endInt >> 16) & 0xff;
            int endG = (endInt >> 8) & 0xff;
            int endB = endInt & 0xff;
            return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
                    (int)((startR + (int)(fraction * (endR - startR))) << 16) |
                    (int)((startG + (int)(fraction * (endG - startG))) << 8) |
                    (int)((startB + (int)(fraction * (endB - startB))));
        }
    }

上面的都是一些系統(tǒng)實現(xiàn)好的估值策略发绢,在內(nèi)部調(diào)用估值器的evaluate方法即可返回改變后的值了。我們也可以自定義估值策略男韧。這里就不展開了朴摊。

當然,在開源框架中此虑,策略模式也是無處不在的。

首先在Volley中口锭,策略模式就能看到朦前。

有一個重試策略接口

    public interface RetryPolicy {
        public int getCurrentTimeout();//獲取當前請求用時(用于 Log)
        public int getCurrentRetryCount();//獲取已經(jīng)重試的次數(shù)(用于 Log)
        public void retry(VolleyError error) throws VolleyError;//確定是否重試,參數(shù)為這次異常的具體信息鹃操。在請求異常時此接口會被調(diào)用韭寸,可在此函數(shù)實現(xiàn)中拋出傳入的異常表示停止重試。
    }

在Volley中荆隘,該接口有一個默認的實現(xiàn)DefaultRetryPolicy恩伺,Volley 默認的重試策略實現(xiàn)類。主要通過在 retry(…) 函數(shù)中判斷重試次數(shù)是否達到上限確定是否繼續(xù)重試椰拒。

public class DefaultRetryPolicy implements RetryPolicy {
 ...
} 

而策略的設置是在Request類中

    public abstract class Request<T> implements Comparable<Request<T>> {
        private RetryPolicy mRetryPolicy;
        public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
            mRetryPolicy = retryPolicy;
            return this;
        }
        public RetryPolicy getRetryPolicy() {
            return mRetryPolicy;
        }
    }

此外晶渠,各大網(wǎng)絡請求框架,或多或少都會使用到緩存燃观,緩存一般會定義一個Cache接口褒脯,然后實現(xiàn)不同的緩存策略,如內(nèi)存緩存缆毁,磁盤緩存等等番川,這個緩存的實現(xiàn),其實也可以使用策略模式。直接看Volley颁督,里面也有緩存践啄。

定義了一個緩存接口

    /**
     * An interface for a cache keyed by a String with a byte array as data.
     */
    public interface Cache {
        /**
         * Retrieves an entry from the cache.
         * @param key Cache key
         * @return An {@link Entry} or null in the event of a cache miss
         */
        public Entry get(String key);
        /**
         * Adds or replaces an entry to the cache.
         * @param key Cache key
         * @param entry Data to store and metadata for cache coherency, TTL, etc.
         */
        public void put(String key, Entry entry);
        /**
         * Performs any potentially long-running actions needed to initialize the cache;
         * will be called from a worker thread.
         */
        public void initialize();
        /**
         * Invalidates an entry in the cache.
         * @param key Cache key
         * @param fullExpire True to fully expire the entry, false to soft expire
         */
        public void invalidate(String key, boolean fullExpire);
        /**
         * Removes an entry from the cache.
         * @param key Cache key
         */
        public void remove(String key);
        /**
         * Empties the cache.
         */
        public void clear();
        /**
         * Data and metadata for an entry returned by the cache.
         */
        public static class Entry {
            /** The data returned from cache. */
            public byte[] data;
            /** ETag for cache coherency. */
            public String etag;
            /** Date of this response as reported by the server. */
            public long serverDate;
            /** The last modified date for the requested object. */
            public long lastModified;
            /** TTL for this record. */
            public long ttl;
            /** Soft TTL for this record. */
            public long softTtl;
            /** Immutable response headers as received from server; must be non-null. */
            public Map<String, String> responseHeaders = Collections.emptyMap();
            /** True if the entry is expired. */
            public boolean isExpired() {
                return this.ttl < System.currentTimeMillis();
            }
            /** True if a refresh is needed from the original data source. */
            public boolean refreshNeeded() {
                return this.softTtl < System.currentTimeMillis();
            }
        }
    }

它有兩個實現(xiàn)類NoCacheDiskBasedCache,使用的時候設置對應的緩存策略即可沉御。

總結(jié)

在android開發(fā)中屿讽,ViewPager是一個使用非常常見的控件,它的使用往往需要伴隨一個Indicator指示器嚷节。如果讓你重新實現(xiàn)一個ViewPager聂儒,并且?guī)в蠭ndicator,這時候硫痰,你會不會想到用策略模式呢衩婚?在你自己寫的ViewPager中(不是系統(tǒng)的,當然你也可以繼承系統(tǒng)的)持有一個策略接口Indicator的變量效斑,通過set方法設置策略非春,然后ViewPager滑動的時候調(diào)用策略接口的對應方法改變指示器。默認提供幾個Indicator接口的實現(xiàn)類缓屠,比如圓形指示器CircleIndicator奇昙、線性指示器LineIndicator、Tab指示器TabIndicator敌完、圖標指示器IconIndicator 等等等等储耐。有興趣的話自己去實現(xiàn)一個吧。

優(yōu)點

  1. 策略模式提供了管理相關(guān)的算法族的辦法滨溉。策略類的等級結(jié)構(gòu)定義了一個算法或行為族什湘。恰當使用繼承可以把公共的代碼移到父類里面,從而避免重復的代碼晦攒。
  2. 策略模式提供了可以替換繼承關(guān)系的辦法闽撤。繼承可以處理多種算法或行為。如果不是用策略模式脯颜,那么使用算法或行為的環(huán)境類就可能會有一些子類哟旗,每一個子類提供一個不同的算法或行為。但是栋操,這樣一來算法或行為的使用者就和算法或行為本身混在一起闸餐。決定使用哪一種算法或采取哪一種行為的邏輯就和算法或行為的邏輯混合在一起,從而不可能再獨立演化讼庇。繼承使得動態(tài)改變算法或行為變得不可能绎巨。
  3. 使用策略模式可以避免使用多重條件轉(zhuǎn)移語句。多重轉(zhuǎn)移語句不易維護蠕啄,它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起场勤,統(tǒng)統(tǒng)列在一個多重轉(zhuǎn)移語句里面戈锻,比使用繼承的辦法還要原始和落后。

缺點

  1. 客戶端必須知道所有的策略類和媳,并自行決定使用哪一個策略類格遭。這就意味著客戶端必須理解這些算法的區(qū)別,以便適時選擇恰當?shù)乃惴惲敉Q言之拒迅,策略模式只適用于客戶端知道所有的算法或行為的情況。
  2. 策略模式造成很多的策略類她倘。有時候可以通過把依賴于環(huán)境的狀態(tài)保存到客戶端里面璧微,而將策略類設計成可共享的,這樣策略類實例可以被不同客戶端使用硬梁。換言之前硫,可以使用享元模式來減少對象的數(shù)量。

適用場景

  1. 如果在一個系統(tǒng)里面有許多類荧止,它們之間的區(qū)別僅在于它們的行為屹电,那么使用策略模式可以動態(tài)地讓一個對象在許多行為中選擇一種行為。
  2. 一個系統(tǒng)需要動態(tài)地在幾種算法中選擇一種跃巡。那么這些算法可以包裝到一個個的具體算法類里面危号,而這些具體算法類都是一個抽象算法類的子類。換言之素邪,這些具體算法類均有統(tǒng)一的接口外莲,由于多態(tài)性原則,客戶端可以選擇使用任何一個具體算法類兔朦,并只持有一個數(shù)據(jù)類型是抽象算法類的對象苍狰。
  3. 一個系統(tǒng)的算法使用的數(shù)據(jù)不可以讓客戶端知道。策略模式可以避免讓客戶端涉及到不必要接觸到的復雜的和只與算法有關(guān)的數(shù)據(jù)烘绽。
  4. 如果一個對象有很多的行為,如果不用恰當?shù)哪J嚼睿@些行為就只好使用多重的條件選擇語句來實現(xiàn)安接。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市英融,隨后出現(xiàn)的幾起案子盏檐,更是在濱河造成了極大的恐慌,老刑警劉巖驶悟,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胡野,死亡現(xiàn)場離奇詭異痕鳍,居然都是意外死亡硫豆,警方通過查閱死者的電腦和手機龙巨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熊响,“玉大人旨别,你說我怎么就攤上這事『骨眩” “怎么了秸弛?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洪碳。 經(jīng)常有香客問我递览,道長,這世上最難降的妖魔是什么瞳腌? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任绞铃,我火速辦了婚禮,結(jié)果婚禮上纯趋,老公的妹妹穿的比我還像新娘憎兽。我一直安慰自己,他們只是感情好吵冒,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布纯命。 她就那樣靜靜地躺著,像睡著了一般痹栖。 火紅的嫁衣襯著肌膚如雪亿汞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天揪阿,我揣著相機與錄音疗我,去河邊找鬼。 笑死南捂,一個胖子當著我的面吹牛吴裤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播溺健,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼麦牺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鞭缭?” 一聲冷哼從身側(cè)響起剖膳,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岭辣,沒想到半個月后吱晒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡沦童,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年仑濒,在試婚紗的時候發(fā)現(xiàn)自己被綠了叹话。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡躏精,死狀恐怖渣刷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情矗烛,我是刑警寧澤辅柴,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站瞭吃,受9級特大地震影響碌嘀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歪架,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一股冗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧和蚪,春花似錦止状、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至催束,卻和暖如春集峦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抠刺。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工塔淤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人速妖。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓高蜂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親罕容。 傳聞我的和親對象是個殘疾皇子妨马,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容