設計模式-策略模式

示例

策略模式是咱們工做中比較經常使用的一個設計模式,可是初次理解起來可能會有點困難,所以咱們仍是先看一個例子,假設如今須要開發一個畫圖工具,畫圖工具中有鋼筆,筆刷和油漆桶,其中,鋼筆能夠用於描邊,但不能填充,筆刷既能夠描邊,也能夠填充,油漆桶只能用於填充,但不能描邊。git

看到這個需求,最容易想到的可能就是經過繼承方式實現了,即鋼筆,筆刷和油漆桶都繼承自畫圖工具,而後都實現描邊和填充功能,只不過鋼筆的填充方法什麼都不作,油漆桶的描邊方法也什麼都不作。(該部分在設計原則中有示例代碼,是當時的一個遺留問題)github

可是仔細一想,好像哪裏不對勁,由於鋼筆和油漆桶部分方法不實現,很明顯違背了里氏替換原則。並且,正常狀況應該是畫圖工具備用鋼筆,筆刷,油漆桶畫圖的能力,而不是鋼筆,筆刷,油漆桶繼承自畫圖工具。所以,咱們能夠以下實現:算法

public class Graphics
{
    public void Stroke(ToolEnum tool)
    {
        switch (tool)
        {
            case ToolEnum.Pen:
                Console.WriteLine($"用鋼筆描邊圖形");
                break;
            case ToolEnum.Brush:
                Console.WriteLine($"用筆刷描邊圖形");
                break;
            case ToolEnum.Bucket:
                Console.WriteLine("油漆桶不能描邊圖形");
                break;
            default:
                throw new NotSupportedException("不支持的畫圖工具");
        }
    }
        
    public void Fill(ToolEnum tool)
    {
        switch (tool)
        {
            case ToolEnum.Pen:
                Console.WriteLine($"鋼筆不能填充圖形");
                break;
            case ToolEnum.Brush:
                Console.WriteLine($"用筆刷填充圖形");
                break;
            case ToolEnum.Bucket:
                Console.WriteLine("用油漆桶填充圖形");
                break;
            default:
                throw new NotSupportedException("不支持的畫圖工具");
        }
    }
}

經過上面枚舉的方式,咱們實現了讓畫圖工具具有描邊和填充的能力,可是這樣的switch-case(或者if-else)給擴展和維護都帶來了很大的麻煩,並且經過前面對其餘設計模式的學習,我相信你們看到這樣的代碼,必定是不能接受的。起碼應該將PenBrushBucket定義成類而且繼承自同一個基類,而後組合到Graphics中來,而不是直接使用條件判斷,由於用組合代替繼承是咱們學習設計模式過程當中百試不爽的經驗。可是,此次好像不怎麼靈了,由於,一旦這樣作,咱們就又回到了原點---鋼筆和油漆桶不得不實現不須要的方法。事實上,用組合替代繼承是沒有錯的,可是該怎麼組合?組合誰呢?這是個問題。感受瞬間陷入了兩難的局面,這時候策略模式就派上用場了,它不抽象鋼筆、筆刷、油漆桶等具體事物,而是直接抽象描邊和填充這兩種能力,站在代碼的角度上看,就是將方法封裝成了對象(正應了那句一切皆對象),這正是策略模式最讓人費解,但又最妙趣橫生的地方。咱們直接看看用策略模式改進後的代碼是怎樣的,先抽象能力:數據庫

public interface IStrokeStrategy
{
    void Stroke();
}

public class PenStrokeStrategy : IStrokeStrategy
{
    public void Stroke()
    {
        Console.WriteLine($"用鋼筆描邊圖形");
    }
}

public class BrushStrokeStrategy : IStrokeStrategy
{
    public void Stroke()
    {
        Console.WriteLine($"用筆刷描邊圖形");
    }
}

public interface IFillStrategy
{
    void Fill();
}

public class BrushFillStrategy : IFillStrategy
{
    public void Fill()
    {
        Console.WriteLine($"用筆刷填充圖形");
    }
}

public class BucketFillStrategy : IFillStrategy
{
    public void Fill()
    {
        Console.WriteLine("用油漆桶填充圖形");
    }
}

看到了嗎?直接將StrokeFill兩種能力定義成了接口,再經過不一樣的子類去實現這種能力。而後再看看Graphics類如何組合:設計模式

public class Graphics
{
    private IStrokeStrategy _strokeStrategy;
    private IFillStrategy _fillStrategy;
        
    public Graphics(IStrokeStrategy strokeStrategy,
                    IFillStrategy fillStrategy)
    {
        this._strokeStrategy = strokeStrategy;
        this._fillStrategy = fillStrategy;
    }

    public void Stroke()
    {
        this._strokeStrategy.Stroke();
    }
        
    public void Fill()
    {
        this._fillStrategy.Fill();
    }
}

畫圖工具直接擁有了兩種能力,可是跟鋼筆、筆刷、油漆桶沒有直接關係,也就是說,只要給畫圖工具一個填充的工具,就能夠完成填充功能了,至於給的具體是筆刷仍是油漆桶,或者其餘什麼東西,畫圖工具並不關心。並且,Graphics類中的條件判斷語句也都去掉了,隔離了變化,整個類都變得穩定了。
以下就是經過策略模式實現的類圖:
緩存

定義

再來看一下定義,策略模式定義一系列算法,把他們一個個封裝起來,而且使他們能夠互相替換。該模式使得算法能夠獨立於使用它的客戶程序而變化。框架

這裏的一系列算法就是不一樣的描邊和填充方式了。不一樣的描邊方式能夠相互替換,不一樣的填充方式也能夠相互替換,而且也能夠方便的擴展更多的描邊和填充方式子類。分佈式

UML類圖

上面的例子中用到了兩組算法,抽象簡化以後就獲得了以下策略模式的UML類圖:
工具

  • Context:策略上下文,持有IStrategy的引用,負責和具體的策略實現交互;
  • IStrategy:策略接口,約束一系列具體的策略算法;
  • ConcreteStrategy:具體的策略實現。

優勢

  • 策略能夠互相替換;
  • 解決switch-caseif-else帶來的難以維護的問題;
  • 策略易於擴展,知足開閉原則.

缺點

  • 客戶端必須知道每個策略類,增長了使用難度。
  • 隨着策略的擴展,策略類數量會增多;

第一個缺點沒法避免,由於策略模式的一大優勢就是算法能夠相互替換,可是若是使用者連每一個算法表明的是什麼意思,優缺點是什麼都不知道,又如何替換呢?但第二個缺點卻能夠經過結合工廠模式,由工廠模式建立具體的策略子類來進行必定程度的緩解,至於具體該怎麼實現,這就是工廠模式的知識了,你們能夠自行回憶一下。學習

應用場景

在業務場景中,商家促銷活動就很是適合用到策略模式了,由於,商家促銷打折可能存在會員折扣,節日折扣,生日折扣等等幾十種方式,並且在不一樣的條件下能夠相互替換。
而非業務場景中,日誌框架中咱們可能會使用log4Net,NLog,Serilog等,而記錄位置也多是控制檯,文件,數據庫等;系統中的緩存,能夠用Redis作分佈式緩存,也能夠用MemeryCache作本地緩存等,這些場景也都很是適合使用策略模式。

總之,策略模式簡約但不簡單,學好它妙用無窮!

源碼連接

相關文章
相關標籤/搜索