策略模式的结构
这个模式涉及到三个角色:
环境(Context)角色:持有一个 Strategy 类的引用。
抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。
上篇博文写的CashSuper 就是抽象策略,而正常收费 CashNormal、打折收费 CashRebate 和返利收费 CashReturn 就是三个具体策略,也就是策略模式中说的具体算法。
附上上篇博文的部分代码
//正常消费,继承CashSuper class CashNormal:CashSuper { public override double acceptCash(double money) { return money; } }
//打折收费消费,继承CashSuper class CashRebate:CashSuper { private double moneyRebate = 1d; //初始化时,必需要输入折扣率,如八折,就是0,8 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.0d; private double moneyReturn = 0.0d; //初始化时必须要输入返利条件和返利值,比如满300返100 //则moneyCondition为300,moneyReturn为100 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; } }
//现金收取父类 abstract class CashSuper { //抽象方法:收取现金,参数为原价,返回为当前价 public abstract double acceptCash(double money); }
加入的策略模式(这里可以弃用工厂模式了)
1 namespace ExtendDiscountOfStrategyPattern 2 { 3 class CashContext 4 { 5 //声明一个现金收费父类对象 6 private CashSuper cs; 7 8 //设置策略行为,参数为具体的现金收费子类(正常,打折或返利) 9 public void setBehavior(CashSuper csuper) 10 { 11 this.cs = csuper; 12 } 13 14 //得到现金促销计算结果(利用了多态机制,不同的策略行为导致不同的结果) 15 public double GetResult(double money) 16 { 17 return cs.acceptCash(money); 18 } 19 } 20 }
但是程序还是少不了switch...case语句,
核心代码(v1.3)
1 //声明一个double变量total来计算总计 2 double total = 0.0d; 3 private void btnConfirm_Click(object sender, EventArgs e) 4 { 5 //声明一个double变量totalPrices 6 double totalPrices = 0d; 7 //策略模式 8 CashContext cc = new CashContext(); 9 switch (cbxType.SelectedItem.ToString()) 10 { 11 case "正常消费": 12 cc.setBehavior(new CashNormal()); 13 break; 14 case "满300返100": 15 cc.setBehavior(new CashReturn("300", "100")); 16 break; 17 case "打8折": 18 cc.setBehavior(new CashRebate("0.8")); 19 break; 20 case "打7折": 21 cc.setBehavior(new CashRebate("0.7")); 22 break; 23 case "打5折": 24 cc.setBehavior(new CashRebate("0.5")); 25 break; 26 } 27 totalPrices = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text)); 28 //将每个商品合计计入总计 29 total = total + totalPrices; 30 //在列表框中显示信息 31 lbxList.Items.Add("单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " 合计:" + totalPrices.ToString()); 32 //在lblTotalShow标签上显示总计数 33 lblTotalShow.Text = total.ToString(); 34 }
最初的策略模式是有缺点的,客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户
端知道所有的算法或行为的情况最初的策略模式是有缺点的,客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
去掉switch...case语句!!!——(本案例采用简单的.net技术:反射)
关键的操作代码为:Assembly.Load(" 程序集名称").CreateInstance(" 名称空间.类名称");
客户端代码(v1.4)
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 //add 11 using CCWin; 12 using System.Data.SqlClient; 13 14 namespace ExtendDiscountOfStrategyPatternWithReflection 15 { 16 using System.Reflection; 17 18 public partial class frmMain :Skin_Metro 19 { 20 21 DataSet ds; //用于存放配置文件信息 22 23 public frmMain() 24 { 25 InitializeComponent(); 26 } 27 28 //声明一个double变量total来计算总计 29 double total = 0.0d; 30 private void btnConfirm_Click(object sender, EventArgs e) 31 { 32 //声明一个double变量totalPrices 33 double totalPrices = 0d; 34 //策略模式 35 CashContext cc = new CashContext(); 36 //根据用户的选项,查询用户选择项的相关行 37 DataRow dr = ((DataRow[])ds.Tables[0].Select("name='" + cbxType.SelectedItem.ToString() + "'"))[0]; 38 //声明一个参数的对象数组 39 object[] args = null; 40 //若有参数,则将其分割成字符串数组,用于实例化时所用的参数 41 if (dr["para"].ToString() != "") 42 { 43 args = dr["para"].ToString().Split(','); 44 } 45 //通过反射实例化出相应的算法对象 46 cc.setBehavior((CashSuper)Assembly.Load("ExtendDiscountOfStrategyPatternWithReflection"). 47 CreateInstance("ExtendDiscountOfStrategyPatternWithReflection." + dr["class"].ToString(), false, 48 BindingFlags.Default, null, args, null, null)); 49 50 totalPrices = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text)); 51 //将每个商品合计计入总计 52 total = total + totalPrices; 53 //在列表框中显示信息 54 lbxList.Items.Add("单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " 合计:" + totalPrices.ToString()); 55 //在lblTotalShow标签上显示总计数 56 lblTotalShow.Text = total.ToString(); 57 } 58 59 private void btnReset_Click(object sender, EventArgs e) 60 { 61 total = 0.0; 62 txtPrice.Text = ""; 63 txtNum.Text = ""; 64 lblTotalShow.Text = ""; 65 lbxList.Items.Clear(); 66 cbxType.SelectedIndex = 0; 67 } 68 69 private void txtNum_KeyPress(object sender, KeyPressEventArgs e) 70 { 71 //数字0~9所对应的keychar为48~57 72 e.Handled = true; 73 //输入0-9 74 if ((e.KeyChar >= 47 && e.KeyChar <= 58) || e.KeyChar == 8) 75 { 76 e.Handled = false; 77 } 78 } 79 80 private void txtPrice_KeyPress(object sender, KeyPressEventArgs e) 81 { 82 //数字0~9所对应的keychar为48~57 83 e.Handled = true; 84 //输入0-9 85 if ((e.KeyChar >= 47 && e.KeyChar <= 58) || (e.KeyChar == 8 || e.KeyChar==46)) 86 { 87 e.Handled = false; 88 } 89 } 90 91 private void frmMain_Load(object sender, EventArgs e) 92 { 93 //读取配置文件 94 ds = new DataSet(); 95 ds.ReadXml(Application.StartupPath + "\\CashAcceptType.xml"); 96 //将读取到的记录绑定到下拉列表框中 97 foreach(DataRowView dr in ds.Tables[0].DefaultView) 98 { 99 cbxType.Items.Add(dr["name"].ToString()); 100 } 101 102 //要下拉选择框在加载的时候,就选择索引为0的元素"正常消费" 103 cbxType.SelectedIndex = 0; 104 } 105 } 106 }
通过程序去读XML的配置文件,来生成这个下拉列表框,然后再根据用户的选择,通过反射实时的实例化出相应的算法对象,最终利用策略模式计算最终的结果。
XML文件——CashAcceptType.xml代码如下
1 xml version="1.0" encoding="utf-8" ?> 2 <CashAcceptType> 3 <type> 4 <name>正常消费name> 5 <class>CashNormalclass> 6 <para>para> 7 type> 8 <type> 9 <name>满300返100name> 10 <class>CashReturnclass> 11 <para>300,100para> 12 type> 13 <type> 14 <name>满200返50name> 15 <class>CashReturnclass> 16 <para>200,50para> 17 type> 18 <type> 19 <name>打8折name> 20 <class>CashRebateclass> 21 <para>0.8para> 22 type> 23 <type> 24 <name>打7折name> 25 <class>CashRebateclass> 26 <para>0.7para> 27 type> 28 <type> 29 <name>打5折name> 30 <class>CashRebateclass> 31 <para>0.5para> 32 type> 33 CashAcceptType>
现在无论需求是什么,用现在的程序,只需要改改XML文件就全部摆平了。比如现在老板觉得现在满300送100太多了,要改成送80,我只需要去XML文件里改就行了。
注:如要添加新的算法,那么该算法类继承CashSuper,再去改一下XML文件就可以了。