【實戰】設計模式應用之策略模式

前言

關於設計模式的文章,園子裏實在是太多太多,並且講解的也很是精彩,那爲何我還要在這裏記錄下這篇文章?本文以實際項目應用「本身動手寫工具--XSmartNote」爲切入點,來說述策略模式的應用。不少初學者都有一種感受,就是在看設計模式相關文章的時候,都看得懂,並且小Demo也是手到擒來,可是就是不知道該怎麼用在實際的項目中,無論你以前有沒有過這種感受,反正我是曾經有過。在前幾天Review Code的時候發現XSmartNote中的主題管理功能很適合這種模式,因而就把這塊相關的代碼重構了一下。在此作一下記錄,一來方便本身,二來惠及他人。html

策略模式

策略模式的用意是針對一組算法或邏輯,將每個算法或邏輯封裝到具備共同接口的獨立的類中,從而使得它們之間能夠相互替換。策略模式使得算法或邏輯能夠在不影響到客戶端的狀況下發生變化。說到策略模式就不得不說起OCP(Open Closed Principle) 開閉原則,即對擴展開放,對修改關閉。策略模式的出現很好地詮釋了開閉原則,有效地減小了分支語句。git

應用場景

下面來講說策略模式的應用場景,如下引自百度百科:程序員

一、 多個類只區別在表現行爲不一樣,可使用Strategy模式,在運行時動態選擇具體要執行的行爲。
二、 須要在不一樣狀況下使用不一樣的策略(算法),或者策略還可能在將來用其它方式來實現。
三、 對客戶隱藏具體策略(算法)的實現細節,彼此徹底獨立。

代碼框架

下面仍是從一個生活中的小例子入手,解釋一下策略模式的大概用法,深刻淺出地理解這個經常使用的設計模式。github

假設老闆有一天忽然對辦公室全部的程序員說,「給大家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 }

以上只是一個簡單的例子,沒有什麼實際的意義,也不是很切題,那爲何還要寫出來?只是讓咱們對策略模式的構成以及應用場景有一個大概的認識,下面我會根據代碼重構的經從來說說策略模式在具體應用程序中的應用。this

策略模式的實踐

在我以前的一篇博文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 }

上述代碼中的兩個方法SetThemeColorSetTextAndBarColor是設置配色方案的主要代碼,傳入的參數就是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

 聲明:本博客原創文字只表明本人工做中在某一時間內總結的觀點或結論,與本人所在單位沒有直接利益關係。非商業,未受權貼子請以現狀保留,轉載時必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。

相關文章
相關標籤/搜索