前言
關於設計模式的文章,園子里實在是太多太多,而且講解的也非常精彩,那為什么我還要在這里記錄下這篇文章?本文以實際項目應用“自己動手寫工具--XSmartNote”為切入點,來講述策略模式的應用。很多初學者都有一種感覺,就是在看設計模式相關文章的時候,都看得懂,而且小Demo也是手到擒來,但是就是不知道該怎么用在實際的項目中,不管你之前有沒有過這種感覺,反正我是曾經有過。在前幾天Review Code的時候發現XSmartNote中的主題管理功能很適合這種模式,於是就把這塊相關的代碼重構了一下。在此做一下記錄,一來方便自己,二來惠及他人。
策略模式
策略模式的用意是針對一組算法或邏輯,將每一個算法或邏輯封裝到具有共同接口的獨立的類中,從而使得它們之間可以相互替換。策略模式使得算法或邏輯可以在不影響到客戶端的情況下發生變化。說到策略模式就不得不提及OCP(Open Closed Principle) 開閉原則,即對擴展開放,對修改關閉。策略模式的出現很好地詮釋了開閉原則,有效地減少了分支語句。
應用場景
下面來說說策略模式的應用場景,以下引自百度百科:
1、 多個類只區別在表現行為不同,可以使用Strategy模式,在運行時動態選擇具體要執行的行為。2、 需要在不同情況下使用不同的策略(算法),或者策略還可能在未來用其它方式來實現。3、 對客戶隱藏具體策略(算法)的實現細節,彼此完全獨立。
代碼框架
下面還是從一個生活中的小例子入手,解釋一下策略模式的大概用法,深入淺出地理解這個常用的設計模式。
假設老板有一天突然對辦公室所有的程序員說,“給你們20天假期,去海南玩吧,經費從我這出!”,這時候辦公室躁動了,哈哈,大家開始商量着怎么去海南,畢竟我們還在帝都呀,畢竟我還沒有去過海南呀,大家七嘴八舌地出起主意來,有人說坐灰機,也有人說坐動車轉海路... ...好了,上面只是一個業務場景,不要想太多了。那怎么實現呢?直接上策略模式,代碼如下:
首先定義一個接口,ITravel包含了一個無返回值的Travel方法
1 interface ITravel 2 { 3 void Travel(); 4 }
然后建立一個維護Travel的上下文,這里應用了單例模式產生TravelContext類,並包含了SetTravel方法用於設置Travel策略,以及Travel方法用於執行策略。代碼如下:
1 class TravelContext 2 { 3 private ITravel _travel=null; 4 private static TravelContext _travelContext; 5 private static object lockObj=new object(); 6 7 private TravelContext(ITravel travel) 8 { 9 this._travel = travel; 10 } 11 12 public static TravelContext CreateTravelContext(ITravel travel) 13 { 14 if (null==_travelContext) 15 { 16 lock (lockObj) 17 { 18 if (null == _travelContext) 19 { 20 _travelContext = new TravelContext(travel); 21 } 22 } 23 } 24 return _travelContext; 25 } 26 27 public void SetTravel(ITravel setTravel) 28 { 29 this._travel = setTravel; 30 } 31 32 public void Travel() 33 { 34 this._travel.Travel(); 35 } 36 }
建立好上下文后,開始建立具體的策略方案,本例中就是幾種Travel的方式,不管以哪種方式執行策略,我們都是在旅行,所以每種策略都實現ITravel接口,並具體實現ITravel接口下的Travel方法,代碼如下:
1 class PlainTravel:ITravel 2 { 3 public void Travel() 4 { 5 Console.WriteLine("我們老大掏腰包,打飛機去海南!"); 6 } 7 } 8 9 class BusTravel:ITravel 10 { 11 public void Travel() 12 { 13 Console.WriteLine("我們老大掏腰包,坐汽車去海南!"); 14 } 15 } 16 17 class BikeTravel : ITravel 18 { 19 public void Travel() 20 { 21 Console.WriteLine("我們老大沒錢了,騎自行車去海南!"); 22 } 23 }
下面來看看客戶端如何使用上述旅行的策略模式,代碼如下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 TravelContext context = TravelContext.CreateTravelContext(new PlainTravel());//打飛機去海南 6 context.Travel();//飛起來 7 context.SetTravel(new BusTravel());//飛機沒油了,坐汽車吧 8 context.Travel();//跑起來 9 context.SetTravel(new BikeTravel());//汽車輪胎扎了... ...騎車去吧 10 context.Travel();//走你 11 Console.ReadLine(); 12 } 13 }

以上只是一個簡單的例子,沒有什么實際的意義,也不是很切題,那為什么還要寫出來?只是讓我們對策略模式的構成以及應用場景有一個大概的認識,下面我會根據代碼重構的經歷來說說策略模式在具體應用程序中的應用。
策略模式的實踐
在我之前的一篇博文XSmartNote里,有這樣的一個功能,就是切換應用的配色方案,當我選擇不同的配色方案時,會執行Switch語句中相應的方案來達到修改配色方案的目的。下面用代碼來解釋這個過程:
1 public void SetTheme(ThemeManager.ThemeEnums.ThemeEnum enums) 2 { 3 switch (enums) 4 { 5 case ThemeManager.ThemeEnums.ThemeEnum.KM_THEME_MISTYROSE: 6 this.BackgroundImage = Resources.bg_10_03; 7 SetThemeColor(Color.MistyRose); 8 SetTextAndBarColor(Color.Maroon, Color.Silver); 9 this.Invalidate(); 10 break; 11 case ThemeManager.ThemeEnums.ThemeEnum.KM_THEME_ALICEBLUE: 12 this.BackgroundImage = Resources.bg_10_04; 13 SetThemeColor(Color.AliceBlue); 14 SetTextAndBarColor(Color.LightBlue, Color.Black); 15 break; 16 case ThemeManager.ThemeEnums.ThemeEnum.KM_THEME_HONEYDEW: 17 this.BackgroundImage = Resources.bg_10_02; 18 SetThemeColor(Color.Honeydew); 19 SetTextAndBarColor(Color.LightGreen, Color.Black); 20 break; 21 case ThemeManager.ThemeEnums.ThemeEnum.KM_THEME_LEMONCHIFFON: 22 this.BackgroundImage = Resources.bg_10_01; 23 SetThemeColor(Color.LemonChiffon); 24 SetTextAndBarColor(Color.Orange, Color.Black); 25 break; 26 default: 27 break; 28 } 29 }
上述代碼中的兩個方法SetThemeColor和SetTextAndBarColor是設置配色方案的主要代碼,傳入的參數就是Color,然后這兩個方法就會變更自己負責的部分的配色方案。下面是具體實現代碼:
1 private void SetThemeColor(Color color) 2 { 3 this.panel_Main.BackColor = color; 4 this.tv_Folder.BackColor = color; 5 this.txt_Title.BoxBackColor = color; 6 this.txt_Content.BoxBackColor = color; 7 } 8 9 private void SetTextAndBarColor(Color color, Color tColor) 10 { 11 this.menuStripHeader.BackColor = color; 12 this.menuStripHeader.ForeColor = tColor; 13 foreach (ToolStripMenuItem item in this.menuStripHeader.Items) 14 { 15 item.ForeColor = tColor; 16 } 17 }
那么什么時候才會去調用這兩個方法以實現配色方案的變更呢?當然是點擊切換主題的時候,代碼如下:
1 private void roseRed_Click(object sender, EventArgs e) 2 { 3 ThemeManager.IThemeManager manager = ThemeManager.ThemeManager.CreateThemeManager(this); 4 manager.ChangeFormTheme(ThemeManager.ThemeEnums.ThemeEnum.KM_THEME_MISTYROSE); 5 } 6 7 private void stoneBlue_Click(object sender, EventArgs e) 8 { 9 ThemeManager.IThemeManager manager = ThemeManager.ThemeManager.CreateThemeManager(this); 10 manager.ChangeFormTheme(ThemeManager.ThemeEnums.ThemeEnum.KM_THEME_ALICEBLUE); 11 }
上述代碼中有一個ThemeManager類負責維護配色方案的功能,接收一個枚舉ThemeEnum來確定要使用哪種配色方案,為了看得更方便,我把ThemeManager類的部分代碼也放在這:
1 public class ThemeManager:IThemeManager 2 { 3 private static ThemeManager themeManager; 4 private MainForm mainForm; 5 private event Action<ThemeEnums.ThemeEnum> ThemeChangeEvent; 6 private static object _lock = new object(); 7 private ThemeManager(MainForm mainForm) 8 { 9 this.mainForm = mainForm; 10 ThemeChangeEvent += mainForm.SetTheme; 11 } 12 13 public static ThemeManager CreateThemeManager(MainForm form) 14 { 15 ThemeManager _themeManager; 16 if (themeManager == null) 17 { 18 lock (_lock) 19 { 20 if (themeManager == null) 21 { 22 _themeManager = new ThemeManager(form); 23 themeManager = _themeManager; 24 } 25 } 26 } 27 return themeManager; 28 } 29 30 public void ChangeFormTheme(ThemeEnums.ThemeEnum enums) 31 { 32 if (ThemeChangeEvent != null) 33 { 34 ThemeChangeEvent(enums); 35 } 36 } 37 }
ThemeManager類的構造函數中綁定了主窗體中的SetTheme方法,也就是我上面貼出的第一段代碼,並在ChangeFormTheme執行的時候調用。到此為止,這一塊的功能大致上就OK啦,但是細心的你可能會發現,如果我又添加了一個配色方案怎么辦?由上面的代碼段可以看出,需要再添加一個枚舉和一個Switch語句分支,問題就出在這里!!!如果要添加10個怎么辦?20個呢?難道要一直修改Switch語句?很明顯,這違背了OCP原則,即對擴展開放,對修改關閉的原則。這時該我們的策略模式上場了,下面是我重構以后的代碼:
首先,建立一個接口ITheme,包含一個SetTheme方法。
1 public interface ITheme 2 { 3 void SetTheme(); 4 }
再建立一個維護Theme的上下文,包含一個ITheme接口的引用和一個SetTheme方法,SetTheme方法中調用實現了ITheme接口的類的SetTheme方法。
public class ThemeContext { ITheme theme = null; public ThemeContext(ITheme myTheme) { theme = myTheme; } public void SetTheme() { theme.SetTheme(); } }
然后就是具體的實現策略,這里實現了具體的設置配色方案的邏輯。
1 public class MistyRose : ITheme 2 { 3 private MainForm _Main; 4 public MistyRose(MainForm main) 5 { 6 this._Main = main; 7 } 8 public void SetTheme() 9 { 10 //實現主題設置 11 _Main.Panel_Main.BackColor = Color.MistyRose; 12 _Main.Tv_Folder.BackColor = Color.MistyRose; 13 _Main.Txt_Title.BoxBackColor = Color.MistyRose; 14 _Main.Txt_Content.BoxBackColor = Color.MistyRose; 15 16 _Main.MenuStripHeader.BackColor = Color.Maroon; 17 _Main.MenuStripHeader.ForeColor = Color.Silver; 18 foreach (ToolStripMenuItem item in _Main.MenuStripHeader.Items) 19 { 20 item.ForeColor = Color.Silver; 21 } 22 } 23 }
下面再看看客戶端是如何使用的。前兩行是之前的調用方式,已經被注釋掉了,最重要的是Switch語句不見了!!!
1 private void roseRed_Click(object sender, EventArgs e) 2 { 3 //ThemeManager.IThemeManager manager = ThemeManager.ThemeManager.CreateThemeManager(this); 4 //manager.ChangeFormTheme(ThemeManager.ThemeEnums.ThemeEnum.KM_THEME_MISTYROSE); 5 6 ITheme theme=new MistyRose(this); 7 ThemeContext themeContext = new ThemeContext(theme); 8 themeContext.SetTheme(); 9 }
如果我想再添加一個主題配色方案該怎么辦?很簡單,添加一個類繼承自ITheme並在客戶端調用就好咯,代碼如下:
1 //添加一個新的配色方案 2 public class AliceBlue : ITheme 3 { 4 private MainForm _Main; 5 public AliceBlue(MainForm main) 6 { 7 this._Main = main; 8 } 9 public void SetTheme() 10 { 11 //實現主題設置 12 _Main.Panel_Main.BackColor = Color.AliceBlue; 13 _Main.Tv_Folder.BackColor = Color.AliceBlue; 14 _Main.Txt_Title.BoxBackColor = Color.AliceBlue; 15 _Main.Txt_Content.BoxBackColor = Color.AliceBlue; 16 17 _Main.MenuStripHeader.BackColor = Color.LightBlue; 18 _Main.MenuStripHeader.ForeColor = Color.Black; 19 foreach (ToolStripMenuItem item in _Main.MenuStripHeader.Items) 20 { 21 item.ForeColor = Color.Black; 22 } 23 } 24 }
//客戶端調用 private void stoneBlue_Click(object sender, EventArgs e) { //ThemeManager.IThemeManager manager = ThemeManager.ThemeManager.CreateThemeManager(this); //manager.ChangeFormTheme(ThemeManager.ThemeEnums.ThemeEnum.KM_THEME_ALICEBLUE); ITheme theme = new AliceBlue(this); ThemeContext themeContext = new ThemeContext(theme); themeContext.SetTheme(); }
這樣就完成了繁雜的Switch語句向策略模式的華麗轉身,如果想看到具體的代碼,請在GitHub上查看。最后放上簡單的效果圖:

總結
以上就是上次重構XSmartNote的過程,經過自己的思考和總結並實際運用到自己的小項目中,收獲還是很大的,至少理解了策略模式在什么時候可以派上用場以及這種模式所解決的問題。可是有人會問,在客戶端調用的時候,還是會new一個具體的對象啊,這樣就會產生依賴,是的,這就是注入依賴要解決的問題咯,本文不做深入的探討。如果文中有什么表述不當的地方,還請大家提出,謝謝大家,另外本文會同步發布到我的簡書。
作者:悠揚的牧笛
博客地址:http://www.cnblogs.com/xhb-bky-blog/p/5535261.html
聲明:本博客原創文字只代表本人工作中在某一時間內總結的觀點或結論,與本人所在單位沒有直接利益關系。非商業,未授權貼子請以現狀保留,轉載時必須保留此段聲明,且在文章頁面明顯位置給出原文連接。
