【实战】设计模式应用之策略模式

一、前言

关于设计模式的文章,园子里实在是太多太多,而且讲解的也非常精彩,那为什么我还要在这里记录下这篇文章?本文以实际项目应用“自己动手写工具--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

声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。

时间: 2024-08-07 21:20:03

【实战】设计模式应用之策略模式的相关文章

设计模式进阶(一) 策略模式

摘自<Design Paterns_Elements of Reusable Object-Oriented Software> 上一系列偏重于入门,从本篇开启进阶系列,着重于设计模式的适用情景. 回顾入门系列 设计模式入门(一)  策略模式 1  Intent Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary

设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也经常遇到类似的情况,实现某一个功能有多种算法或者策略,我们能够依据环境或者条件的不同选择不同的算法或者策略来完毕该功能.如查找.排序等,一种经常使用的方法是硬编码(Hard Coding)在一个类中,如须要提供多种查找算法,能够将这些算法写到一个类中,在该类中提供多个方法,每个方法相应一个详细的查找算法:当然也能够将这些查找算法封装在一个统一的方法中,通过if-else-或者case等条件推断语句来进行选择.

设计模式实现C++ --策略模式Strategy(对象行为型)

1.问题 出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机.每个策略都可以得到相同的结果,但是它们使用了不同的资源.选择策略的依据 是费用,时间,使用工具还有每种方式的方便程度. 2.解决方案 策略模式:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换.本模式使得算法可独立于使用它的客户而变化. 策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想. strategy模式类图: 3.应用场景 1. 

从“假如有以下几种价格10,20,50,请你代码实现将他们排序输出”看着设计模式中的策略模式

今天重温了一下策略模式,将自己的一些感悟与大家分享...本人只是技术渣渣,所理解的东西的难免会有很大的局限性甚至是错误,还请各位带着批判的眼光去看待....不喜请勿吐槽 定义:策略模式属于设计模式中的对象行为型模式,它将用到的算法单独抽象成一个单独的类.通常,我们在多个类完成同一件事情,仅仅完成的方式不同时,我们可以考虑使用这种设计模式. 举例:相信,很多人在看到"假如有以下几种价格10,20,50,请你代码实现将他们排序输出"这种题目的时候,很快就写出了以下代码,写之前还不忘了问一下

【转】设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能.如查找.排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法:当然也可以将这些查找算法封装在一个统一的方法中,通过if-else-或者case等条件判断语句来进行选择.这

设计模式-行为型-策略模式

策略模式(Strategy): 策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理.策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类.用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”. 策略模式的角色: 1)环境类(Context):采用组合或聚合的方式维护一个对Strategy对象的引用. 2)抽象策略类(Strategy):定义所有支持的算法的公共接口.Context使用这个接口来调用某Concret

设计模式(一):策略模式

一.设计背景 现实生活中,我们要做一件事情或者完成某项工作,往往有很多种途径.比如我们出游,可以选择坐汽车,坐火车,土豪点的选择是坐飞机.还有我们现在线下的支付方式也有了很多种选择,以前在外面忘了带钱的话可能一瓶水都难以买到,现在只要我们手机在身上,可以用微信或者支付宝. 在软件设计层面,我们把各种支付方式叫做策略.如果不考虑设计模式的话,我们可能会在一个类中用if..else方式来选择支付方式,即 if(type == 1){ //微信支付 }else if(type == 2){ // 支付

[设计模式] javascript 之 策略模式

定义: 封装一系列的算法,使得他们之间可以相互替换,本模式使用算法独立于使用它的客户的变化. 说明:策略模式,是一种组织算法的模式,核心不在于算法,而在于组织一系列的算法,并且如何去使用它:策略模式作用在于,行为实现的不可预见,面对这样的一种变化,我们得思考如何使用程序好维跟扩展,并使得客户很好的使用算法的方式: 策略模式 使用要注意它 "变化" 的一面,策略模式就是来解决这个 变化 问题的. 比如商场买卖的价格或促销问题,如果不使用模式,就可能只是 把“所有”的情况用 if else

head first 设计模式(-) 策略模式(鸭子)

目的:减少依赖 设计模式对应不同的需求,设计原则则代表永恒的灵魂,在实践中未必时刻遵守,但要时刻牢记. 1.依赖倒转原则(Dependence Inversion Principle) 2.接口隔离原则(Interface Segregation Principle) 3.里氏代换原则(Liskov Substitution Principle) 4.开闭原则(Open Close Principle) 5.迪米特法则(Demeter Principle) 6.合成复用原则(Composite