第七课 策略模式
今天来看看策略模式吧。首先,策略模式定义:
策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。
简单的来说呢,可以这么想想,现在由一个类,里面有个方法function,我想在实例化这个类的时候,能够传一个方法给function,让实例类使用传入方法。这样,就相当于把这个方法提取出来,有点像是方法指针的这么一个东西。
其实.net里面方法是可以直接传值的,通过delegate 来实现,但是咱们这里主要讨论面向对象通用的处理方法,就是策略模式。
来看一下策略模式的基本实现思路:
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4.
5. namespace 策略模式
6. {
7. class Program
8. {
9. static void Main (string[] args)
10. {
11. Context context;
12.
13. context = new Context(new ConcreteStrategyA());
14. context.ContextInterface();
15.
16. context = new Context(new ConcreteStrategyB());
17. context.ContextInterface();
18.
19. context = new Context(new ConcreteStrategyC());
20. context.ContextInterface();
21.
22. Console.Read();
23. }
24. }
25.
26. //抽象算法类
27. abstract class Strategy
28. {
29. //算法方法
30. public abstract void AlgorithmInterface();
31. }
32. //具体算法A
33. class ConcreteStrategyA : Strategy
34. {
35. //算法A实现方法
36. public override void AlgorithmInterface()
37. {
38. Console.WriteLine("算法A实现");
39. }
40. }
41. //具体算法B
42. class ConcreteStrategyB : Strategy
43. {
44. //算法B实现方法
45. public override void AlgorithmInterface()
46. {
47. Console.WriteLine("算法B实现");
48. }
49. }
50. //具体算法C
51. class ConcreteStrategyC : Strategy
52. {
53. //算法C实现方法
54. public override void AlgorithmInterface()
55. {
56. Console.WriteLine("算法C实现");
57. }
58. }
59. //上下文
60. class Context
61. {
62. Strategy strategy;
63.
64. public Context(Strategy strategy)
65. {
66. this.strategy = strategy;
67. }
68. //上下文接口
69. public void ContextInterface()
70. {
71. strategy.AlgorithmInterface();
72. }
73. }
74.
75. }
76.
1. 抽象算法类:Strategy
来看下这个类,里面只定义了一个抽象方法AlgorithmInterface ,可以把它想象成一个方法指针,指向了一个方法的起始点。
2. 再来看具体的实现类对象:ConcreteStrategyA,ConcreteStrategyB,ConcreteStrategyC
他们集成Strategy ,并给予了AlgorithmInterface这个方法不同的实现。
3. 接下来是:Context 类,
它包含一个 Strategy 成员 ,拥有一个构造函数,传入一个Strategy 来初始化自己的成员。最后定义了一个ContextInterface()方法,里面只是调用了Strategy 成员的AlgorithmInterface 这个方法。(打眼看起来有点像是工厂方法加模板模式的应用哦)
4. 最后看程序主方法的使用方式:
5. context = new Context(new ConcreteStrategyA());
6. context.ContextInterface();
用ConcreteStrategyA构造了Context对象context,直接调用方法ContextInterface。
现在考虑,这调用的是那个方法呢?会输出什么呢?
答案揭晓~
Console.WriteLine("算法A实现");
程序会走这一句,输出“算法A实现”。相信大家看到这里后,应当对继承的应用有一定了解了吧,里氏代换原则哦~
我如果用ConcreteStrategyA构造Context的话,ContextInterface则会调用
Console.WriteLine("算法B实现");
依此类推了,相信C就不用我说了。
现在实际就相当于那Context类当作一个方法指针来用。我用哪个实现类来初始化它,它就指向那个类实现的方法。借此达到了对方法(策略)的封装。
那么策略方法有什么用呢,这里我觉得大话设计模式中的例子就很好,来看一下。
例子:实现一个商场管理软件,界面如下
输入单价数量,选择计算方式,然后输出价格。
来看实现代码:
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4.
5. namespace 商场管理软件
6. {
7. //现金收取父类
8. abstract class CashSuper
9. {
10. //抽象方法:收取现金,参数为原价,返回为当前价
11. public abstract double acceptCash(double money);
12. }
13.
14. }
15.
这是策略类的基类,封装了我们要的付款算法。
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4.
5. namespace 商场管理软件
6. {
7. //正常收费,继承CashSuper
8. class CashNormal : CashSuper
9. {
10. public override double acceptCash(double money)
11. {
12. return money;
13. }
14. }
15.
16. }
17.
这是正常收费的算法。直接返回钱数。
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4.
5. namespace 商场管理软件
6. {
7. //打折收费,继承CashSuper
8. class CashRebate : CashSuper
9. {
10. private double moneyRebate = 1d;
11. //初始化时,必需要输入折扣率,如八折,就是0.8
12. public CashRebate(string moneyRebate)
13. {
14. this.moneyRebate = double.Parse(moneyRebate);
15. }
16.
17. public override double acceptCash(double money)
18. {
19. return money * moneyRebate;
20. }
21. }
22. }
23.
这是打折时的算法,需要初始化折扣,返回打折后钱数。
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4.
5. namespace 商场管理软件
6. {
7. //返利收费,继承CashSuper
8. class CashReturn : CashSuper
9. {
10. private double moneyCondition = 0.0d;
11. private double moneyReturn = 0.0d;
12. //初始化时必须要输入返利条件和返利值,比如满300返100,则moneyCondition为300,moneyReturn为100
13. public CashReturn(string moneyCondition, string moneyReturn)
14. {
15. this.moneyCondition = double.Parse(moneyCondition);
16. this.moneyReturn = double.Parse(moneyReturn);
17. }
18.
19. public override double acceptCash(double money)
20. {
21. double result = money;
22. //若大于返利条件,则需要减去返利值
23. if (money >= moneyCondition)
24. result = money - Math.Floor(money / moneyCondition) * moneyReturn;
25.
26. return result;
27. }
28. }
29. }
30.
这是返利的算法,设定多少返回多少哦。
然后看Context类:
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4.
5. namespace 商场管理软件
6. {
7. //现金收取工厂
8. class CashContext
9. {
10. CashSuper cs = null;
11.
12. //根据条件返回相应的对象
13. public CashContext(string type)
14. {
15. switch (type)
16. {
17. case "正常收费":
18. CashNormal cs0 = new CashNormal();
19. cs = cs0;
20. break;
21. case "满300返100":
22. CashReturn cr1 = new CashReturn("300", "100");
23. cs = cr1;
24. break;
25. case "打8折":
26. CashRebate cr2 = new CashRebate("0.8");
27. cs = cr2;
28. break;
29. }
30. }
31.
32. public double GetResult(double money)
33. {
34. return cs.acceptCash(money);
35. }
36. }
37. }
38.
注意到没有,这里不是简单的策略算法,他结合了简单工厂模式哦。将使用算法的判断逻辑也封装了进来。通过GetResult()方法来计算哦。
最后看一下界面实现吧。(为了能完整运行例子)
1. using System;
2. using System.Collections.Generic;
3. using System.ComponentModel;
4. using System.Data;
5. using System.Drawing;
6. using System.Text;
7. using System.Windows.Forms;
8.
9. namespace 商场管理软件
10. {
11. public partial class Form1 : Form
12. {
13. public Form1()
14. {
15. InitializeComponent();
16. }
17.
18. //客户端窗体程序(主要部分)
19. double total = 0.0d;
20. private void btnOk_Click(object sender, EventArgs e)
21. {
22. //利用简单工厂模式根据下拉选择框,生成相应的对象
23. CashContext csuper = new CashContext(cbxType.SelectedItem.ToString());
24. double totalPrices = 0d;
25. //通过多态,可以得到收取费用的结果
26. totalPrices = csuper.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
27. total = total + totalPrices;
28. lbxList.Items.Add("单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " "
29. + cbxType.SelectedItem + " 合计:" + totalPrices.ToString());
30. lblResult.Text = total.ToString();
31. }
32.
33. private void btnClear_Click(object sender, EventArgs e)
34. {
35. total = 0d;
36. txtPrice.Text = "0.00";
37. txtNum.Text = "0";
38. lbxList.Items.Clear();
39. lblResult.Text = "0.00";
40. }
41. }
42. }
相关的UI生成那部分我就不贴了,相信大家都能看懂了。
知道为什么要结合简单工厂了吧,就是为了简化UI曾逻辑。进一步降低耦合性哦。
43. CashContext csuper = new CashContext(cbxType.SelectedItem.ToString());
这一句很有用吧,哈哈。
你还发现什么好处没有,我们定义了这么多算法类,但是客户端呢,只需要告诉她CashContext这个简单工厂+策略的实现类就可以了,耦合性很低吧,通用性很高吧~
策略模式介绍到此结束,还是老话,设计模式需要自己体会其中的好处,当你感受到了,自然就会理解了。试着实现下这个商场管理软件吧。(最好根据思路自己来敲代码哦)
本课到此结束。
作者:王文斌
转载请注明出处哦~
突然想到,这里其实还是可以结合工厂方法理解少的那种动态反射方法的吗,改进一下哦。
首先这里CashContext这个类要改一下。
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4.
5. namespace 商场管理软件
6. {
7. class CashContext
8. {
9. private CashSuper cs;
10.
11. public void setBehavior(CashSuper csuper)
12. {
13. this.cs = csuper;
14. }
15.
16. public double GetResult(double money)
17. {
18. return cs.acceptCash(money);
19. }
20. }
21. }
22.
这里用了纯正的策略实现。
你可能要有疑问了,那我怎么传入具体策略的实现对象呢?
接着往下瞧哦。
首先,我们要想最大程度的提高可扩展性,减少代码修改工作,就应该考虑能都太配置。简单的说就是把后台代码转移到程序的配置文件的明文。这在工厂模式哪里介绍过了。,net和java都是支持的。动态Load一个类对象出来。
下面看本例的实现方式。
界面UI类:
1. using System;
2. using System.Collections.Generic;
3. using System.ComponentModel;
4. using System.Data;
5. using System.Drawing;
6. using System.Text;
7. using System.Windows.Forms;
8. using System.Reflection;
9.
10. namespace 商场管理软件
11. {
12. public partial class Form1 : Form
13. {
14. public Form1()
15. {
16. InitializeComponent();
17. }
18.
19. DataSet ds;//用于存放配置文件信息
20. double total = 0.0d;//用于总计
21.
22. private void Form1_Load(object sender, EventArgs e)
23. {
24. //读配置文件
25. ds = new DataSet();
26. ds.ReadXml(Application.StartupPath + "//CashAcceptType.xml");
27. //将读取到的记录绑定到下拉列表框中
28. foreach (DataRowView dr in ds.Tables[0].DefaultView)
29. {
30. cbxType.Items.Add(dr["name"].ToString());
31. }
32. cbxType.SelectedIndex = 0;
33. }
34.
35. private void btnOk_Click(object sender, EventArgs e)
36. {
37. CashContext cc = new CashContext();
38. //根据用户的选项,查询用户选择项的相关行
39. DataRow dr = ((DataRow[])ds.Tables[0].Select("name='" + cbxType.SelectedItem.ToString()+"'"))[0];
40. //声明一个参数的对象数组
41. object[] args =null;
42. //若有参数,则将其分割成字符串数组,用于实例化时所用的参数
43. if (dr["para"].ToString() != "")
44. args = dr["para"].ToString().Split(',');
45. //通过反射实例化出相应的算法对象
46. cc.setBehavior((CashSuper)Assembly.Load("商场管理软件").CreateInstance("商场管理软件." + dr["class"].ToString(), false, BindingFlags.Default, null, args, null, null));
47.
48. double totalPrices = 0d;
49. totalPrices = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
50. total = total + totalPrices;
51. lbxList.Items.Add("单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " "+cbxType.SelectedItem+ " 合计:" + totalPrices.ToString());
52. lblResult.Text = total.ToString();
53. }
54.
55. private void btnClear_Click(object sender, EventArgs e)
56. {
57. total = 0d;
58. txtPrice.Text = "0.00";
59. txtNum.Text = "1";
60. lbxList.Items.Clear();
61. lblResult.Text = "0.00";
62. }
63.
64.
65. }
66. }
看出点门道来了吗,这里通过读取一个文件来确定用什么类。这样我们就吧逻辑判断转移到了配置文件中。相应的增加一个配置文件,这里采取的是xml文件,如下:
1. <?xml version="1.0" encoding="utf-8" ?>
2. <CashAcceptType>
3. <type>
4. <name>正常收费</name>
5. <class>CashNormal</class>
6. <para></para>
7. </type>
8. <type>
9. <name>满300返100</name>
10. <class>CashReturn</class>
11. <para>300,100</para>
12. </type>
13. <type>
14. <name>满200返50</name>
15. <class>CashReturn</class>
16. <para>200,50</para>
17. </type>
18. <type>
19. <name>打8折</name>
20. <class>CashRebate</class>
21. <para>0.8</para>
22. </type>
23. <type>
24. <name>打7折</name>
25. <class>CashRebate</class>
26. <para>0.7</para>
27. </type>
28. </CashAcceptType>
29.
效果是什么呢?
你自己试试吧,我就给你个图~
好的,补充到此结束。
谢谢捧场~
作者:王文斌