博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入浅出设计模式系列(二):策略模式
阅读量:5145 次
发布时间:2019-06-13

本文共 10117 字,大约阅读时间需要 33 分钟。

端午节到了,Jacky在家没事做,就跑去自己学长Cook的家串门。两个人聊得甚欢,Cook突然兴起,说,Jacky,今天我让你做个程序题,你想不想做。踌躇满志想成为一个优秀的程序员的Jacky当然毫不犹豫的就答应了。

Jacky:来吧,什么题目啊?没有难度的题目可别出哦!

Cook:你小子,长成了是吧?别看到时候出的题目简单,里面学问可不少呢。

Jacky:好吧,那你就别废话了,快说什么题目?

Cook:嗯,是这样的,就做一个商场的收营程序,程序要记录买的商品单价、商品数量,商品价格并且计算出商品的总价。

Jacky:就这个啊?那还不容易啊?不就是在一个窗体上放两个文本框分别用来填写商品单价、商品数量,然后用一个列表框来记录商品的清单,一个标签来记录总计,一个确定按钮来算出每种商品的费用。对了,还要一个重置按钮,来重新开始。

说着Jacky就去写代码了,不一会儿,半小时不到代码就写好了。他是这么写的

1 private void btnOK_Click(object sender, EventArgs e) 2 { 3     double total = 0.0d; 4  5     double totalPrice = Convert.ToDouble(txtProductNumber.Text) * Convert.ToDouble(txtProductUnitPrice.Text); 6  7     total += totalPrice; 8  9     lvProducts.Items.Add("商品数量:" + txtProductNumber.Text + " 商品单价:" + txtProductUnitPrice.Text + " 商品总价:" + totalPrice.ToString());10 11     lblTotal.Text += total.ToString();12 }

 

Cook看了看Jacky写的代码,摇了摇头对Jacky说

Cook:你看看,你这么写的话,如果我要现在又有一个新的需求,商店端午小长假促销活动,所有商品打8折,那你要怎么办啊?

Jacky:那也很简单啊,只有修改一行代码就行了啊,只要把代码改成下面那样就Ok了,这样就是打八折了。

1 double totalPrice = Convert.ToDouble(txtProductNumber.Text) * Convert.ToDouble(txtProductUnitPrice.Text) * 0.8;

Cook:那如果端午节过了呢?不打折了,你这套程序岂不是会让商场蒙受巨大的损失啊?

Jacky:那么到时候不打折了,我再把程序改回来就OK了啊。

Cook:现在一年四季那么多节日,打折促销活动又层出不穷的。一会儿打折让利,一会儿满300返100的活动让利,又一会儿消费满200积分50的活动,轮番上阵,那你岂不是得累死啊?跟着他们不停的修改程序,部署客户端,这样你不觉得麻烦吗?你不觉得麻烦,人家商场的领导都觉得麻烦了。

Jacky:啊。。。我好像是想的有些太简单了。那要不我增加一个下拉框,添加一些既定的打着方式,到时候让他们营业员来选择,这样就可以省掉一些麻烦事了。

就这样,Jacky又跑回电脑旁去改写自己刚才的程序,添加了一个让利下拉框,供客户程序进行选择。他是这么写的

1 private void btnOK_Click(object sender, EventArgs e) 2 { 3     double total = 0.0d; 4     double totalPrice = 0.0d; 5  6     switch (cmbType.SelectedText) 7     { 8         case "正常收费": 9             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice);10             break;11         case "五折":12             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) * 0.5;13             break;14         case "六折":15             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) * 0.6;16             break;17         case "七折":18             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) * 0.7;19             break;20         case "八折":21             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) * 0.8;22             break;23         case "满300减100":24             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) - Math.Floor(Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) / 300) * 100;25             break;26         default:27             break;28     }29 30     total += totalPrice;31 32     lvProducts.Items.Add("商品数量:" + txtProductNumber.Text + " 商品单价:" + txtProductUnitPrice.Text + " 商品总价:" + totalPrice.ToString());33 34     lblTotal.Text += total.ToString();35 }

Jacky兴高采烈的跑去让Cook看他改进后的代码

Jacky:Cook,你快来,快来帮我看看,我把程序改进过了。

Cook:嗯,我看看,哦,这样子写的确是比先前的代码要灵活些了。不过还是不够好,你看看你的代码,打5折和打6折,7折,8折有什么区别?就是最后的乘数不同,你干嘛要写那么多重复的代码?看看能不能进行封装一下啊?看来我之前教你的,都白教了,你都给忘记了。

Jacky:哦哦,对哦,这个可以使用简单工厂方式,是吧?

一溜烟,Jacky又跑去电脑边写起了代码,很快他就写好了代码,拿来给Cook看,他是这么写的

1 //抽象产品类  2 public abstract class CashSuper  3 {  4     public abstract double AcceptCash(double money);  5 }  6   7 //正常收费产品类  8 public class CashNomal : CashSuper  9 { 10     public override double AcceptCash(double money) 11     { 12         return money; 13     } 14 } 15  16 //打折产品类 17 public class CashDiscount : CashSuper 18 { 19     public double Rate { get; set; } 20  21     public CashDiscount(double rate) 22     { 23         this.Rate = rate; 24     } 25  26     public override double AcceptCash(double money) 27     { 28         return money * Rate; 29     } 30 } 31  32 //返现产品类 33 public class CashBack : CashSuper 34 { 35     public double CashBackCondition { get; set; } 36     public double BackMoney { get; set; } 37  38     public CashBack(double cashBackCondition, double backMoney) 39     { 40         this.CashBackCondition = cashBackCondition; 41         this.BackMoney = backMoney; 42     } 43  44     public override double AcceptCash(double money) 45     { 46         return money - (money / CashBackCondition) * BackMoney; 47     } 48 } 49  50 //工厂类 51 public class PayMoneyFactory 52 { 53     public static CashSuper CreateAcceptCash(string payType) 54     { 55         CashSuper cashSuper = null; 56  57         switch (payType) 58         { 59             case "正常收费": 60                 cashSuper = new CashNomal(); 61                 break; 62             case "五折": 63                 cashSuper = new CashDiscount(0.5); 64                 break; 65             case "六折": 66                 cashSuper = new CashDiscount(0.6); 67                 break; 68             case "七折": 69                 cashSuper = new CashDiscount(0.7); 70                 break; 71             case "八折": 72                 cashSuper = new CashDiscount(0.8); 73                 break; 74             case "满300减100": 75                 cashSuper = new CashBack(300, 100); 76                 break; 77             case "满200减50": 78                 cashSuper = new CashBack(200, 50); 79                 break; 80         } 81  82         return cashSuper; 83     } 84 } 85  86 //客户端代码 87 private void btnOK_Click(object sender, EventArgs e) 88 { 89     double total = 0.0d; 90     double totalPrice = 0.0d; 91  92     CashSuper cashSuper = PayMoneyFactory.CreateAcceptCash(cmbType.SelectedText); 93  94     totalPrice = cashSuper.AcceptCash(Convert.ToDouble(txtProductNumber.Text) * Convert.ToDouble(txtProductUnitPrice.Text));             95  96     total += totalPrice; 97  98     lvProducts.Items.Add("商品数量:" + txtProductNumber.Text + " 商品单价:" + txtProductUnitPrice.Text + " 商品总价:" + totalPrice.ToString()); 99 100     lblTotal.Text += total.ToString();101 }

Cook:嗯,我看看代码,Jacky写的不错啊,有长进,看来现在你对简单工厂模式掌握的不错啊。。。

Jacky:那是当然的,你也不看看我是谁?

Cook:好了,别又得意忘形了。简单工厂模式虽然也能解决这个问题,但这个模式只能解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场可能经常性的更改打折额度,每次维护和扩展收费方式都要改动这个工厂,以致于代码需要重新编译部署,这真是很糟糕的处理方式,所以用它不是最好的办法。面对算法的时常变动,应该可以有更好的办法。今天我就教你一个新招。

Jacky:又是什么新招?什么设计模式?你就别再卖关子了,快告诉我吧。

Cook:嗯,那就是策略模式。来,看我来给你写代码。

1 //抽象策略角色  2 public abstract class CashSuper  3 {  4     public abstract double AcceptCash(double money);  5 }  6   7 //正常收费策略  8 public class CashNomal : CashSuper  9 { 10     public override double AcceptCash(double money) 11     { 12         return money; 13     } 14 } 15  16 //打折策略 17 public class CashDiscount : CashSuper 18 { 19     public double Rate { get; set; } 20  21     public CashDiscount(double rate) 22     { 23         this.Rate = rate; 24     } 25  26     public override double AcceptCash(double money) 27     { 28         return money * Rate; 29     } 30 } 31  32 //返现策略 33 public class CashBack : CashSuper 34 { 35     public double CashBackCondition { get; set; } 36     public double BackMoney { get; set; } 37  38     public CashBack(double cashBackCondition, double backMoney) 39     { 40         this.CashBackCondition = cashBackCondition; 41         this.BackMoney = backMoney; 42     } 43  44     public override double AcceptCash(double money) 45     { 46         return money - (money / CashBackCondition) * BackMoney; 47     } 48 } 49  50 //环境角色,上下文,持有一个策略类的引用,供最终给客户端调用 51 public class CashContext 52 { 53     private CashSuper cashSuper = null; 54  55     public CashContext(string payType) 56     { 57         switch (payType) 58         { 59             case "正常收费": 60                 cashSuper = new CashNomal(); 61                 break; 62             case "五折": 63                 cashSuper = new CashDiscount(0.5); 64                 break; 65             case "六折": 66                 cashSuper = new CashDiscount(0.6); 67                 break; 68             case "七折": 69                 cashSuper = new CashDiscount(0.7); 70                 break; 71             case "八折": 72                 cashSuper = new CashDiscount(0.8); 73                 break; 74             case "满300减100": 75                 cashSuper = new CashBack(300, 100); 76                 break; 77             case "满200减5100": 78                 cashSuper = new CashBack(200, 50); 79                 break; 80         } 81     } 82  83     public double GetPayResult(double money) 84     { 85         return cashSuper.AcceptCash(money); 86     } 87 } 88  89 //客户端代码 90 private void btnOK_Click(object sender, EventArgs e) 91 { 92     double total = 0.0d; 93     double totalPrice = 0.0d; 94  95     CashContext context = new CashContext(cmbType.SelectedText); 96     totalPrice = context.GetPayResult(Convert.ToDouble(txtProductNumber.Text) * Convert.ToDouble(txtProductUnitPrice.Text)); 97  98     total += totalPrice; 99 100     lvProducts.Items.Add("商品数量:" + txtProductNumber.Text + " 商品单价:" + txtProductUnitPrice.Text + " 商品总价:" + totalPrice.ToString());101 102     lblTotal.Text += total.ToString();103 }

Cook:看像这样商场收营如何促销,是打折还是返利,其实都是一些算法,算法本身只是一个策略,最重要这些算法随时都可能互相替换的,这就是变化点,封装变化点就是我们面向对象的一种重要的思维方式。这里有一个抽象类,这个抽象类定义了一个获取收营金额的方法,然后不同的策略算法类来继承自这个抽象类,实现这个抽象类的抽象方法计算收营金额,在CashContext类中,维护一个收营的父类对象,根据收营方式的不同,动态的调用不同策略类计算收营的方法。

 

策略模式(Strategy Pattern)

策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。(原文: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.)

Context(应用场景):

  1. 需要使用ConcreteStrategy提供的算法。 
  2. 内部维护一个Strategy的实例。 
  3. 负责动态设置运行时Strategy具体的实现算法。 
  4. 负责跟Strategy之间的交互和数据传递。 

Strategy(抽象策略类): 

  1. 定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类实现。 

ConcreteStrategy(具体策略类): 

  1. 实现了Strategy定义的接口,提供具体的算法实现。

 

策略模式的组成

  • 抽象策略角色: 策略类,通常由一个接口或者抽象类实现。 
  • 具体策略角色:包装了相关的算法和行为。 
  • 环境角色:持有一个策略类的引用,最终给客户端调用。

 

策略模式的应用场景 

  1. 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。(例如FlyBehavior和QuackBehavior) 
  2. 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。(例如FlyBehavior和QuackBehavior的具体实现可任意变化或扩充) 
  3. 对客户(Duck)隐藏具体策略(算法)的实现细节,彼此完全独立。

 

策略模式的优点

  • 提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活(算法独立,可以任意扩展)。 
  • 避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。 
  • 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。 

 

策略模式的缺点

  • 由于每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量

 

文章声明本文部分内容参考自《大话设计模式》,这是一本学习设计模式非常好的书。

转载于:https://www.cnblogs.com/HLiang/archive/2013/06/14/strategy_pattern.html

你可能感兴趣的文章
ANTLR4权威指南 - 第5章 设计语法
查看>>
Python enum 枚举 判断 key(键) 或者 value(值)是否在枚举中
查看>>
python3 的一些常见的小知识点
查看>>
rpm 校验
查看>>
搭建eureka服务
查看>>
二叉树
查看>>
c++浅复制和深复制
查看>>
在一个view类里面获取viewcontroller
查看>>
我的框架说明文档 2016-04-06
查看>>
【C/C++开发】C++ Thread对象封装
查看>>
【VS开发】VSTO 学习笔记(十)Office 2010 Ribbon开发
查看>>
【并行计算-CUDA开发】从熟悉到精通 英伟达显卡选购指南
查看>>
【计算机视觉】背景建模之PBAS
查看>>
http header Content-Type之常用三种
查看>>
[转]ab参数详解 – 压力测试
查看>>
线程ThreadDemo04
查看>>
windows平台下node,npm,gulp配置
查看>>
《C#并行编程高级教程》第5章 协调数据结构 笔记
查看>>
android开发 NDK 编译和使用静态库、动态库 (转)
查看>>
数学之美
查看>>