策略模式

不问归期 提交于 2020-02-08 01:12:44

一、概念

策略模式(Strategy):它定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法的变化不会影响到使用算法的客户。(原文:The Strategy Pattern defines a family of algorithms,encapsulates each one,and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.)

 

 

图1 策略模式类图

 优点:

 

  1、 简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
  2、 避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。
      3、 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。

 

  缺点:
  1、 因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量。
      2、 在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象。(这本身没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由Context来承担,这就最大化的减轻了客户端的压力。)

参考阅读: 
1.  2. 

 二、我的理解

其实这个策略模式和简单工厂模式一样,仅仅是对面向对象继承中常用的Override技术的一种应用。简单工厂模式中有一个工厂类,负责根据输入参数的不同来生成不同的子类,并将生成的子类作为基类返回(这样可以让客户端在使用的时候很方便)。客户端只需要调用工厂类创建一个基类的实例,然后调用这个实例的函数来实现自己的功能。而策略模式中的Context类的功能基本上就是对工厂类的强化版本,它也负责根据输入参数来生成不同的类,只是它并不返回生成类,而是将生成类所实现的功能接口包装一次,提供给客户。这样对客户来说,他们只需要知道这一个Context类就可以完成他们想要的功能,而不必再知道其他的信息。

三、策略模式与简单工厂模式结合的代码实现

下面以一个简单的商场收银系统为例,演示一下策略模式与简单工厂模式结合的使用。我将这个系统用两个工程实现。一个工程实现商场计费的业务功能,另一个工程用于实现POS机上的界面,也就是客户端。

首先介绍第一个工程:

1. 实现计费功能的基类(这里用抽象类实现): 


1 namespace ColinSong.DesignPattern.Strategy
2 {
3     public abstract class BillingAlgorithm
4     {
5         public abstract double getBillingResult(double price, int quantity);
6     }
7 }

2. 实现具体计费功能的子类:

     2.1 实现打折计费的子类:     


 1 namespace ColinSong.DesignPattern.Strategy
 2 {
 3     public class BillingStrategy_Rebate:BillingAlgorithm
 4     {
 5         double discounts;
 6         public BillingStrategy_Rebate(double discounts)
 7         {
 8             if (discounts < 0.0000001 || discounts >= 1)
 9             {
10                 this.discounts = 1;
11             }
12             else
13             {
14                 this.discounts = discounts;
15             }
16         }
17 
18         public override double getBillingResult(double price, int quantity)
19         {
20             return price * quantity * discounts;
21         }
22     }
23 }

     2.2 实现返现计费功能的子类:     


 1 namespace ColinSong.DesignPattern.Strategy
 2 {
 3     public class BillingStrategy_CashReturn:BillingAlgorithm
 4     {
 5         int CashCondition;
 6         int CashReturn;
 7         public BillingStrategy_CashReturn(int CashCondition, int CashReturn)
 8         {
 9             if (CashCondition <= 0)
10             {
11                 CashCondition = 1;
12             }
13             if (CashReturn <= 0)
14             {
15                 CashReturn = 1;
16             }
17             this.CashCondition = CashCondition;
18             this.CashReturn = CashReturn;
19         }
20 
21         public override double getBillingResult(double price, int quantity)
22         {
23             double orignal = price * quantity;
24             int n = (int) (orignal / CashCondition);
25             return orignal - CashReturn * n;
26         }
27     }
28 }
29 

3. Context类


 1namespace ColinSong.DesignPattern.Strategy
 2{
 3    public class Billing
 4    {
 5        //维护一个算法基类的实例
 6        BillingAlgorithm billingAlgorithm;
 7
 8        //简单工厂模式的构造函数
 9        public Billing(string BillingStrategy)
10        {
11            switch (BillingStrategy)
12            {
13                case "打8折":
14                    billingAlgorithm = new BillingStrategy_Rebate(0.8);
15                    break;
16                case "满200返40":
17                    billingAlgorithm = new BillingStrategy_CashReturn(200, 40);
18                    break;
19                default:
20                    billingAlgorithm = new BillingStrategy_Rebate(1);
21                    break;
22            }
23        }
24        //策略模式的构造函数
25        public Billing(BillingAlgorithm billingAlgorithm)
26        {
27            this.billingAlgorithm = billingAlgorithm;
28        }
29        //这是策略模式的典型特征
30        public double GetResult(double price, int quantity)
31        {
32            return billingAlgorithm.getBillingResult(price, quantity);
33        }
34    }
35}
36

 

好,算法完成了,下面介绍客户端,界面如图2所示:

图2. 商场收银系统界面

 

下面看一下,确定按钮后面的代码:


        private void btnOK_Click(object sender, EventArgs e)
        {
            \\只需要知道这个Context类,实例化它;
            Billing billing = new Billing(cbxBillingType.Text);
            \\并调用它提供的方法,即可完成我们需要的功能。
            double charge = billing.GetResult(double.Parse(txtPrice.Text), 
                int.Parse(txtQuantity.Text));
            totalCash = totalCash + charge;
            string itemShow = "单价:"+txtPrice.Text
                + "\t数量:"+txtQuantity.Text +
                ".\t实收:"+ charge.ToString()
                + "\t"+cbxBillingType.Text;
            list.Items.Add(itemShow);
            lblSum.Text = totalCash.ToString();
        }

 

 

 

 

应用场景分析:

容错恢复机制
        容错恢复机制是应用程序开发中非常常见的功能。那么什么是容错恢复呢?简单点说就是:程序运行的时候,正常情况下应该按照某种方式来做,如果按照某种方式来做发生错误的话,系统并不会崩溃,也不会就此不能继续向下运行了,而是有容忍出错的能力,不但能容忍程序运行出现错误,还提供出现错误后的备用方案,也就是恢复机制,来代替正常执行的功能,使程序继续向下运行。
        举个实际点的例子吧,比如在一个系统中,所有对系统的操作都要有日志记录,而且这个日志还需要有管理界面,这种情况下通常会把日志记录在数据库里面,方便后续的管理,但是在记录日志到数据库的时候,可能会发生错误,比如暂时连不上数据库了,那就先记录在文件里面,然后在合适的时候把文件中的记录再转录到数据库中。
        对于这样的功能的设计,就可以采用策略模式,把日志记录到数据库和日志记录到文件当作两种记录日志的策略,然后在运行期间根据需要进行动态的切换。
        在这个例子的实现中,要示范由上下文来选择具体的策略算法,前面的例子都是由客户端选择好具体的算法,然后设置到上下文中。
        下面还是通过代码来示例一下。
(1)先定义日志策略接口,很简单,就是一个记录日志的方法,示例代码如下:

/**

 * 日志记录策略的接口

 */

public interface LogStrategy {

    /**

     * 记录日志

     * @param msg 需记录的日志信息

     */

    public void log(String msg);

}
 


(2)实现日志策略接口,先实现默认的数据库实现,假设如果日志的长度超过长度就出错,制造错误的是一个最常见的运行期错误,示例代码如下:

/**

 * 把日志记录到数据库

 */

public class DbLog implements LogStrategy{

    public void log(String msg) {     

       //制造错误

       if(msg!=null && msg.trim().length()>5){

           int a = 5/0;

       }

       System.out.println("现在把 '"+msg+"' 记录到数据库中");

    }

}
 

接下来实现记录日志到文件中去,示例代码如下:

/**

 * 把日志记录到文件

 */

public class FileLog implements LogStrategy{

    public void log(String msg) {

       System.out.println("现在把 '"+msg+"' 记录到文件中");

    }

}
 

(3)接下来定义使用这些策略的上下文,注意这次是在上下文里面实现具体策略算法的选择,所以不需要客户端来指定具体的策略算法了,示例代码如下:

 

(4)看看现在的客户端,没有了选择具体实现策略算法的工作,变得非常简单,故意多调用一次,可以看出不同的效果,示例代码如下:

 

 (5)小结一下,通过上面的示例,会看到策略模式的一种简单应用,也顺便了解一下基本的容错恢复机制的设计和实现。在实际的应用中,需要设计容错恢复的系统一般要求都比较高,应用也会比较复杂,但是基本的思路是差不多的。

 作者演示了策略的一种变体。

这个例子同时说明了设计中的另外一个重要的概念,单一职责原理。

单一职责原则(SRP),就一个类而言应该仅有一个引起它变化的原因。

如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力,这种耦合会导致脆弱的设计

当变化发生时,设计会遭受到意想不到的破坏。

 

总结

策略模式:行为型模式

策略模式的重点:Context 上下文类,根据传入的参数,做出正确的行为

 

工厂模式:创建型模式

工厂模式的重点:factory类,根据环境要求,实例化相应的操作类

 

工厂模式和策略模式的区别  

工厂模式是为了实例化对象,策略模式是为了做出某种行为

 

 

文章转载

http://www.cnblogs.com/colinsong/archive/2009/03/02/1401723.html

http://www.cnblogs.com/skyme/archive/2011/04/12/2009223.html

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!