讀《大話設計模式》——應用策略模式的"商場收銀系統"(WinForm)
策略模式的結構
這個模式涉及到三個角色:
環境(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); }View Code
加入的策略模式(這裡可以棄用工廠模式了)
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>CashNormal</class> 6 <para></para> 7 </type> 8 <type> 9 <name>滿300返100</name> 10 <class>CashReturn</class> 11 <para>300,100</para> 12 </type> 13 <type> 14 <name>滿200返50</name> 15 <class>CashReturn</class> 16 <para>200,50</para> 17 </type> 18 <type> 19 <name>打8折</name> 20 <class>CashRebate</class> 21 <para>0.8</para> 22 </type> 23 <type> 24 <name>打7折</name> 25 <class>CashRebate</class> 26 <para>0.7</para> 27 </type> 28 <type> 29 <name>打5折</name> 30 <class>CashRebate</class> 31 <para>0.5</para> 32 </type> 33 </CashAcceptType>
現在無論需求是什麼,用現在的程式,只需要改改XML檔案就全部擺平了。比如現在老闆覺得現在滿300送100太多了,要改成送80,我只需要去XML檔案裡改就行了。
注:如要新增新的演算法,那麼該演算法類繼承CashSuper,再去改一下XML檔案就可以