Bootstrap

大话设计模式 | 2. 策略模式

》是作者「程杰」通过趣味的场景设置,以诙谐的表达来解读和剖析「面向对象」编程思维和「设计模式」。书中的示例代码是以 .NET 的 C# 语言编写而成。

本文是我对《大话设计模式》的学习系列笔记的第二篇,策略模式。

1. 定义

策略模式 (Strategy Pattern),定义了算法家族,分别封装起来(封装变化点),让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

策略模式减少了各种算法类与使用算法类之间的耦合,使得:

2. 组成

与简单工厂模式,策略模式主要是由三个角色组成:

  • 「抽象策略(Strategy)」角色,是「具体策略」的父类,定义所有支持的算法的公共接口。

  • 「具体策略(ConcreteStrategy)」角色,封装了具体的算法或行为。

  • 「环境(Context)」角色,用一个具体策略来配置,维护一个对抽象策略对象的引用。

3. 使用步骤

主要包含四步:

4. 实例

《大话设计模式》中,是通过一个商场的收银系统为例进行说明的。商场收银系统需要能够处理正常收费、商品打折和节假日满减等各种活动。

这里正常收费、打折和满减是该系统需要实现的具体算法,他们都共用的一个收费的接口。

创建抽象策略类

创建 的抽象策略类,并定义所支持的算法的公共接口 。

abstract class CashSuper
{
  public abstract double acceptCash(double money);
}

创建具体策略类

创建具体策略类(、、 等),来继承 的抽象策略类,并实现 方法,来定义具体的算法。

class CashNormal : CashSuper
    {
        public override double acceptCash(double money)
        {
            return money;
        }
    }

    class CashRebate : CashSuper
    {
        private double moneyRebate = 1;
        public CashRebate(string moneyRebate)
        {
            this.moneyRebate = double.Parse(moneyRebate);
        }

        public override double acceptCash(double money)
        {
            return money * moneyRebate;
        }
    }

    class CashReturn : CashSuper
    {
        private double moneyCondition = 0;
        private double moneyReturn = 0;
        public CashReturn(string moneyCondition, string moneyReturn)
        {
            this.moneyCondition = double.Parse(moneyCondition);
            this.moneyReturn = double.Parse(moneyReturn);
        }
        public override double acceptCash(double money)
        {
            double result = money;
            if (money >= moneyCondition)
            {
                result = money - Math.Floor(money / moneyCondition) * moneyReturn;
            }
            return result;
        }
    }

创建环境类

通过构造方法,传入具体的收费策略,并根据具体的策略对象来调用其算法,得到不同的计算结果。

class CashContext
{
  private CashSuper cashsuper; //声明一个CashSuper对象
  public CashContext(CashSuper cashsuper)
  {
    this.cashsuper = cashsuper;
  }

  public double GetResult(double money)
  {
    return cashsuper.acceptCash(money);
  }
}

调用环境类的方法

在主函数中(客户端),通过传入相应的策略对象,并调用环境类的 方法,来得到收费的结果,让具体的算法与客户端进行了隔离。

但是,这里存在一个很大的问题,虽然具体的算法与客户端进行了隔离,但是还是要在客户端去判断需用用哪一个算法。

...
CashSuper cashsuper = null;
switch (type)
{
  case "0":
    cashsuper = new CashContext(new CashNormal());
    break;
  case "满百返百":
    cashsuper = new CashContext(new CashReturn());
    break;
  case "8折":
    cashsuper = new CashContext(new CashDebate());
    break;
}

double totalPrice = 0;

totalPrice = cashsuper.GetResult(price * number);

total += totalPrice;
...

与简单工厂模式结合

在简单工厂模式中,我们是通过创建工厂类,并定义静态方法,通过传入不同参数来创建不同具体产品类的实例。

那么,我们也可以将实例化的具体策略过程,通过简单工厂的应用,从客户端转移到环境类中。

class CashContext
{
  CashSuper cashsuper = null;
  public CashContext(string type) //这里参数由原来的具体策略对象,变成了收费类型的字符串
  {
    switch (type)
    {
      case "0":
        CashNormal cashNormal = new CashNormal();
        cashsuper = cashNormal;
        break;
      case "满百返百":
        CashReturn cashReturn = new CashReturn("300", "100");
        cashsuper = cashReturn;
        break;
      case "8折":
        CashRebate cashRebate = new CashRebate("0.8");
        cashsuper = cashRebate;
        break;
    }
  }

  public double GetResult(double money)
  {
    return cashsuper.acceptCash(money);
  }
}

相应的,客户端代码就比较简单了,只需调用环境类,传入收费类型即可。

...
Console.Write("Please input the discount: 0/满百返百/8折 ");
string discount = Console.ReadLine();

CashContext cashsuper = new CashContext(discount);

double totalPrice = 0;

totalPrice = cashsuper.GetResult(price * number);

total += totalPrice;
...

在客户端的代码中,对比简单工厂模式,只需认识一个 类就可以了。而如果通过简单工厂模式的话,客户端需要认识 和 两个类。这使得具体的收费算法彻底与客户端分离了。

5. 参考

本文参考了这两篇文章:

具体代码可参考: