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

用户头像
Puran
关注
发布于: 2020 年 06 月 13 日
大话设计模式 | 2. 策略模式

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



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

1. 定义

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



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

  1. 算法可独立于使用该算法的客户端而进行变化

  2. 客户端可以根据外部条件选择不同的策略来解决不同的问题(算法由客户端选择)

2. 组成

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

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

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

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



3. 使用步骤

主要包含四步:

  1. 创建「抽象策略」类,并定义所有支持的算法的公共接口;

  2. 创建「具体策略」类,并定义具体的算法实现;

  3. 创建「环境」类,通过构造方法,传入具体的策略参数,并根据具体的策略对象来调用其算法;

  4. 外界通过调用「环境」类的方法,通过传入不同参数来实例化不同的策略,从而得到不同的结果。



4. 实例

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

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

创建抽象策略类

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

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



创建具体策略类

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

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);
}
}



调用环境类的方法

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



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



...
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;
...



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



5. 参考

本文参考了这两篇文章:



具体代码可参考:https://github.com/puran1218/DesignPatternWithCSharp

发布于: 2020 年 06 月 13 日 阅读数: 51
用户头像

Puran

关注

GIS从业者,正在往开发的路上小跑。 2018.03.29 加入

从业4年的GIS开发小白,work@esri。

评论

发布
暂无评论
大话设计模式 | 2. 策略模式