讀《大話設計模式》——應用策略模式的"商場收銀系統"(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);
}
加入的策略模式(這裡可以棄用工廠模式了)
namespace ExtendDiscountOfStrategyPattern
{
class CashContext
{
//宣告一個現金收費父類物件
private CashSuper cs; //設定策略行為,引數為具體的現金收費子類(正常,打折或返利)
public void setBehavior(CashSuper csuper)
{
this.cs = csuper;
} //得到現金促銷計算結果(利用了多型機制,不同的策略行為導致不同的結果)
public double GetResult(double money)
{
return cs.acceptCash(money);
}
}
}
但是程式還是少不了switch...case語句,
核心程式碼(v1.3)
//宣告一個double變數total來計算總計
double total = 0.0d;
private void btnConfirm_Click(object sender, EventArgs e)
{
//宣告一個double變數totalPrices
double totalPrices = 0d;
//策略模式
CashContext cc = new CashContext();
switch (cbxType.SelectedItem.ToString())
{
case "正常消費":
cc.setBehavior(new CashNormal());
break;
case "滿300返100":
cc.setBehavior(new CashReturn("", ""));
break;
case "打8折":
cc.setBehavior(new CashRebate("0.8"));
break;
case "打7折":
cc.setBehavior(new CashRebate("0.7"));
break;
case "打5折":
cc.setBehavior(new CashRebate("0.5"));
break;
}
totalPrices = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
//將每個商品合計計入總計
total = total + totalPrices;
//在列表框中顯示資訊
lbxList.Items.Add("單價:" + txtPrice.Text + " 數量:" + txtNum.Text + " 合計:" + totalPrices.ToString());
//在lblTotalShow標籤上顯示總計數
lblTotalShow.Text = total.ToString();
}
最初的策略模式是有缺點的,客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味著客戶端必須理解這些演演算法的區別,以便適時選擇恰當的演演算法類。換言之,策略模式只適用於客戶
端知道所有的演演算法或行為的情況最初的策略模式是有缺點的,客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味著客戶端必須理解這些演演算法的區別,以便適時選擇恰當的演演算法類。換言之,策略模式只適用於客戶端知道所有的演演算法或行為的情況。
去掉switch...case語句!!!——(本案例採用簡單的.net技術:反射)
關鍵的操作程式碼為:Assembly.Load(" 程式集名稱").CreateInstance(" 名稱空間.類名稱");
客戶端程式碼(v1.4)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
//add
using CCWin;
using System.Data.SqlClient; namespace ExtendDiscountOfStrategyPatternWithReflection
{
using System.Reflection; public partial class frmMain :Skin_Metro
{ DataSet ds; //用於存放配置檔案資訊 public frmMain()
{
InitializeComponent();
} //宣告一個double變數total來計算總計
double total = 0.0d;
private void btnConfirm_Click(object sender, EventArgs e)
{
//宣告一個double變數totalPrices
double totalPrices = 0d;
//策略模式
CashContext cc = new CashContext();
//根據使用者的選項,查詢使用者選擇項的相關行
DataRow dr = ((DataRow[])ds.Tables[].Select("name='" + cbxType.SelectedItem.ToString() + "'"))[];
//宣告一個引數的物件陣列
object[] args = null;
//若有引數,則將其分割成字串陣列,用於例項化時所用的引數
if (dr["para"].ToString() != "")
{
args = dr["para"].ToString().Split(',');
}
//通過反射例項化出相應的演演算法物件
cc.setBehavior((CashSuper)Assembly.Load("ExtendDiscountOfStrategyPatternWithReflection").
CreateInstance("ExtendDiscountOfStrategyPatternWithReflection." + dr["class"].ToString(), false,
BindingFlags.Default, null, args, null, null)); totalPrices = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
//將每個商品合計計入總計
total = total + totalPrices;
//在列表框中顯示資訊
lbxList.Items.Add("單價:" + txtPrice.Text + " 數量:" + txtNum.Text + " 合計:" + totalPrices.ToString());
//在lblTotalShow標籤上顯示總計數
lblTotalShow.Text = total.ToString();
} private void btnReset_Click(object sender, EventArgs e)
{
total = 0.0;
txtPrice.Text = "";
txtNum.Text = "";
lblTotalShow.Text = "";
lbxList.Items.Clear();
cbxType.SelectedIndex = ;
} private void txtNum_KeyPress(object sender, KeyPressEventArgs e)
{
//數字0~9所對應的keychar為48~57
e.Handled = true;
//輸入0-9
if ((e.KeyChar >= && e.KeyChar <= ) || e.KeyChar == )
{
e.Handled = false;
}
} private void txtPrice_KeyPress(object sender, KeyPressEventArgs e)
{
//數字0~9所對應的keychar為48~57
e.Handled = true;
//輸入0-9
if ((e.KeyChar >= && e.KeyChar <= ) || (e.KeyChar == || e.KeyChar==))
{
e.Handled = false;
}
} private void frmMain_Load(object sender, EventArgs e)
{
//讀取配置檔案
ds = new DataSet();
ds.ReadXml(Application.StartupPath + "\\CashAcceptType.xml");
//將讀取到的記錄繫結到下拉列表框中
foreach(DataRowView dr in ds.Tables[].DefaultView)
{
cbxType.Items.Add(dr["name"].ToString());
} //要下拉選擇框在載入的時候,就選擇索引為0的元素"正常消費"
cbxType.SelectedIndex = ;
}
}
}
通過程式去讀XML的配置檔案,來生成這個下拉列表框,然後再根據使用者的選擇,通過反射實時的例項化出相應的演演算法物件,最終利用策略模式計算最終的結果。
XML檔案——CashAcceptType.xml程式碼如下
<?xml version="1.0" encoding="utf-8" ?>
<CashAcceptType>
<type>
<name>正常消費</name>
<class>CashNormal</class>
<para></para>
</type>
<type>
<name>滿300返100</name>
<class>CashReturn</class>
<para>300,100</para>
</type>
<type>
<name>滿200返50</name>
<class>CashReturn</class>
<para>200,50</para>
</type>
<type>
<name>打8折</name>
<class>CashRebate</class>
<para>0.8</para>
</type>
<type>
<name>打7折</name>
<class>CashRebate</class>
<para>0.7</para>
</type>
<type>
<name>打5折</name>
<class>CashRebate</class>
<para>0.5</para>
</type>
</CashAcceptType>
現在無論需求是什麼,用現在的程式,只需要改改XML檔案就全部擺平了。比如現在老闆覺得現在滿300送100太多了,要改成送80,我只需要去XML檔案裡改就行了。
注:如要新增新的演演算法,那麼該演演算法類繼承CashSuper,再去改一下XML檔案就可以了。