策略模式是咱們工做中比較經常使用的一個設計模式,可是初次理解起來可能會有點困難,所以咱們仍是先看一個例子,假設如今須要開發一個畫圖工具,畫圖工具中有鋼筆,筆刷和油漆桶,其中,鋼筆能夠用於描邊,但不能填充,筆刷既能夠描邊,也能夠填充,油漆桶只能用於填充,但不能描邊。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
)給擴展和維護都帶來了很大的麻煩,並且經過前面對其餘設計模式的學習,我相信你們看到這樣的代碼,必定是不能接受的。起碼應該將Pen
,Brush
,Bucket
定義成類而且繼承自同一個基類,而後組合到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("用油漆桶填充圖形"); } }
看到了嗎?直接將Stroke
和Fill
兩種能力定義成了接口,再經過不一樣的子類去實現這種能力。而後再看看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類圖:
工具
IStrategy
的引用,負責和具體的策略實現交互;switch-case
、if-else
帶來的難以維護的問題;第一個缺點沒法避免,由於策略模式的一大優勢就是算法能夠相互替換,可是若是使用者連每一個算法表明的是什麼意思,優缺點是什麼都不知道,又如何替換呢?但第二個缺點卻能夠經過結合工廠模式,由工廠模式建立具體的策略子類來進行必定程度的緩解,至於具體該怎麼實現,這就是工廠模式的知識了,你們能夠自行回憶一下。學習
在業務場景中,商家促銷活動就很是適合用到策略模式了,由於,商家促銷打折可能存在會員折扣,節日折扣,生日折扣等等幾十種方式,並且在不一樣的條件下能夠相互替換。
而非業務場景中,日誌框架中咱們可能會使用log4Net
,NLog
,Serilog
等,而記錄位置也多是控制檯,文件,數據庫等;系統中的緩存,能夠用Redis
作分佈式緩存,也能夠用MemeryCache
作本地緩存等,這些場景也都很是適合使用策略模式。
總之,策略模式簡約但不簡單,學好它妙用無窮!