1. 程式人生 > >讀《大話設計模式》——應用策略模式的"商場收銀系統"(WinForm)

讀《大話設計模式》——應用策略模式的"商場收銀系統"(WinForm)

策略模式的結構

這個模式涉及到三個角色:

環境(Context)角色:持有一個 Strategy 類的引用。
抽象策略(Strategy)角色:這是一個抽象角色,通常由一個介面或抽象類實現。此角色給出所有的具體策略類所需的介面。
具體策略(ConcreteStrategy)角色:包裝了相關的演算法或行為。

 

 

上篇博文寫的CashSuper 就是抽象策略,而正常收費 CashNormal、打折收費 CashRebate 和返利收費 CashReturn 就是三個具體策略,也就是策略模式中說的具體演算法。

   附上上篇博文的部分程式碼

//正常消費,繼承CashSuper
    class CashNormal:CashSuper
    {
        public override double acceptCash(double money)
        {
            return money;
        }
    }
View Code
//打折收費消費,繼承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;
        }
    }
View Code
//返利收費
    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;
        }
    }
View Code
//現金收取父類
    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檔案就可以