設(shè)計(jì)模式中的策略模式與狀態(tài)模式
使用場(chǎng)景
狀態(tài)模式:當(dāng)對(duì)象的行為隨對(duì)象的狀態(tài)的改變而改變時(shí),我們?yōu)榱私怦疃嘀嘏袛鄺l件,封裝行為的變化,可以定義一個(gè)抽象的狀態(tài)類,提供對(duì)象行為接口。具體與狀態(tài)相關(guān)的行為,由它的子類去實(shí)現(xiàn)。
策略模式:“策略”二字等同于算法,當(dāng)現(xiàn)實(shí)系統(tǒng)中需要對(duì)算法動(dòng)態(tài)指定,并且可以互相替換,可以抽象出算法的調(diào)用接口,具體的算法實(shí)現(xiàn)由具體的策略角色去實(shí)現(xiàn),根據(jù)里氏替換原則,任何出現(xiàn)父類的地方都可以使用它的子類去替換,這樣符合我們的業(yè)務(wù)需求。
比較
雖然兩者都是抽象出父類規(guī)范調(diào)用接口,具體的行為由子類實(shí)現(xiàn),環(huán)境對(duì)象同時(shí)包含父類的引用,但是這兩者模式應(yīng)用的場(chǎng)景完全不同。例如:你去ATM機(jī)取款,如果你的賬戶處于凍結(jié)狀態(tài),就不能正常取款。這里你的銀行賬戶至少有兩個(gè)狀態(tài)就是凍結(jié)與非凍結(jié)。這樣的業(yè)務(wù)解決方案你應(yīng)該不會(huì)想到用策略模式去解決。又如商場(chǎng)打折,有很多打折的策略,兒童用品打7折,老人用品打5折,這個(gè)問(wèn)題不會(huì)跟狀態(tài)模式扯上關(guān)系吧,這里商品你不能說(shuō)有老人狀態(tài)和兒童狀態(tài)!
ATM取款案例分析
設(shè)計(jì)到的角色:取款人、賬戶、ATM機(jī)。
用例圖
這邊簡(jiǎn)單起見(jiàn),就分析取款用例。
基本事件路徑
(1) 取款人插入銀行卡,輸入密碼;
(2) 選擇取款操作,輸入取款金額;
(3) 等待出鈔,取款。
在這個(gè)用例中,如果取款人賬戶已凍結(jié),就發(fā)生一個(gè)可選事件路徑,取款用例就被終止。
關(guān)鍵的業(yè)務(wù)需求是用戶取款事實(shí),所以領(lǐng)域建??梢詮臉?biāo)識(shí)取款和取款人這兩個(gè)類開(kāi)始。這邊考慮到取款人不僅有取款這個(gè)用例,我們將取款泛化成交易。
建模
用例實(shí)化
取款:基本事件路徑的實(shí)化。
細(xì)化領(lǐng)域模型
這邊引入了ATMSystem這個(gè)邊界對(duì)象作為控制器負(fù)責(zé)與用戶交互(與用戶交互的ATM操作界面),Trade是負(fù)責(zé)處理用戶交易的類。
取款人插入銀行卡輸入密碼,ATMSystem負(fù)責(zé)將驗(yàn)證賬戶信息的消息傳遞給Trade交易類,Trade根據(jù)傳過(guò)來(lái)的賬戶密碼驗(yàn)證用戶是否存在,然后將處理結(jié)束的消息傳遞給ATMSystem這個(gè)邊界對(duì)象反映給用戶。
分析模型中的一些類
取款設(shè)計(jì)——應(yīng)用狀態(tài)模式
賬戶的狀態(tài)分為凍結(jié)狀態(tài)和激活狀態(tài)。賬戶在激活的狀態(tài)下可以進(jìn)行交易,而在凍結(jié)狀態(tài)下不能做任何交易。在賬戶驗(yàn)證的時(shí)候,應(yīng)該返回當(dāng)前賬戶的狀態(tài),如果賬戶處于凍結(jié)狀態(tài),當(dāng)取款人進(jìn)行取款操作的時(shí)候直接返回錯(cuò)誤提示信息。狀態(tài)圖表示如下:
取款應(yīng)用狀態(tài)模式結(jié)構(gòu)圖:
在驗(yàn)證賬戶階段,如果賬戶處于凍結(jié)狀態(tài),則直接返回DeadAccount謝絕一切交易,否則返回ActiveAccount處理后面的交易。
示意代碼:
這邊為了簡(jiǎn)單實(shí)現(xiàn),省去了ATMSystem這個(gè)邊界對(duì)象。
用戶賬號(hào)信息抽象類
- /// <summary>
- /// 用戶賬號(hào)信息
- /// </summary>
- public abstract class Account
- {
- /// <summary>
- /// 賬號(hào)
- /// </summary>
- private string account;
- /// <summary>
- /// 密碼
- /// </summary>
- private string pwd;
- public abstract decimal getBalance(decimal d);
- }
凍結(jié)賬號(hào)類
- /// <summary>
- /// 凍結(jié)賬戶類
- /// </summary>
- public class DeadAccount:Account
- {
- public override decimal getBalance(decimal d)
- {
- return 0;
- }
- }
激活賬戶類
- /// <summary>
- /// 激活賬戶類
- /// </summary>
- public class ActiveAccount:Account
- {
- public override decimal getBalance(decimal d)
- {
- return d;
- }
- }
交易類
- /// <summary>
- /// 交易類 負(fù)責(zé)用戶交易處理的類
- /// </summary>
- public class Trade
- {
- /// <summary>
- /// 保存用戶賬號(hào)信息
- /// </summary>
- private Account account;
- public Account VolidateLogin(Account a)
- {
- //query the database to validate the user exists
- //For Example
- this.account = new DeadAccount();
- return this.account;
- }
- /// <summary>
- /// 商業(yè)邏輯 取款
- /// </summary>
- /// <param name="d"></param>
- /// <returns></returns>
- public decimal GetBalance(decimal d)
- {
- return this.account.getBalance(d);
- }
- }
用戶類
- /// <summary>
- /// 取款人
- /// </summary>
- public class User
- {
- /// <summary>
- /// 用戶賬號(hào)信息
- /// </summary>
- private Account account;
- /// <summary>
- /// 交易處理類
- /// </summary>
- private Trade trade;
- public User(Account a, Trade t)
- {
- this.account = a;
- tthis.trade = t;
- }
- /// <summary>
- /// 用戶登錄類
- /// </summary>
- public void Login()
- {
- trade.VolidateLogin(account);
- }
- /// <summary>
- /// 取款
- /// </summary>
- /// <param name="d"></param>
- /// <returns></returns>
- public decimal GetBalance(decimal d)
- {
- return trade.GetBalance(d);
- }
- }
客戶端代碼
- class Client
- {
- static void Main(string[] args)
- {
- //開(kāi)始用戶取款,默認(rèn)是激活賬戶
- ActiveAccount aa = new ActiveAccount();
- Trade t = new Trade();
- User u = new User(aa,t);
- //先登錄,后取款
- u.Login();
- Console.WriteLine(u.GetBalance(100));
- Console.ReadLine();
- }
- }
用戶必須先登錄(插入銀行卡,輸入密碼,點(diǎn)確定),才能選擇取款業(yè)務(wù)(選擇取款)。登錄之后,返回的賬號(hào)對(duì)象作為trade類成員,當(dāng)進(jìn)行取款業(yè)務(wù)的時(shí)候直接引用該賬號(hào)成員獲得取款信息。如果該賬號(hào)屬于凍結(jié)賬號(hào),則直接返回0。否則返回取款的值。
商品折扣案例
案例描述:某家超市國(guó)慶節(jié)為了促銷,某些類商品打折,比如運(yùn)動(dòng)鞋打8折、秋裝打9折等,張三去購(gòu)物為了一雙運(yùn)動(dòng)鞋、一件秋裝、一瓶洗發(fā)水。。。,張三買完?yáng)|西回家,心想今天自己總共“賺”了多少錢?
案例分析:商家對(duì)于商品打折可能有很多策略,這里使用策略模式,封裝商品打折策略,這樣以便以后擴(kuò)展了打折策略,不用去修改原來(lái)的代碼,具有很好的靈活性。
模式涉及的角色:
抽象策略角色:通常由一個(gè)接口或者抽象實(shí)現(xiàn);
具體策略角色:包裝了相關(guān)的算法和行為;
環(huán)境角色:包含抽象策略角色的引用,最終供用戶使用。
商品折扣案例設(shè)計(jì)圖
顧客購(gòu)物涉及到的角色有:購(gòu)物車、商品、收銀臺(tái)、抽象策略角色和具體策略角色。
購(gòu)物車:是擺放商品的容器,提供添加和刪除操作;
商品:商品實(shí)體,有類型,商品名、價(jià)格等屬性;
收銀臺(tái):負(fù)責(zé)收錢,主要是計(jì)算顧客購(gòu)買所有商品的價(jià)格和折扣總額;
抽象策略角色:提供折扣策略接口。
具體策略角色:實(shí)現(xiàn)具體折扣算法。
商品折扣示意代碼:
- /// <summary>
- /// 具體商品類
- /// </summary>
- public class goods
- {
- /// <summary>
- /// 商品類型
- /// </summary>
- public string Type
- {
- set;
- get;
- }
- /// <summary>
- /// 商品名稱
- /// </summary>
- public string Name
- {
- get;
- set;
- }
- /// <summary>
- /// 商品價(jià)格
- /// </summary>
- public decimal Price
- {
- get;
- set;
- }
- }
抽象策略角色
- /// <summary>
- /// 抽象策略接口
- /// </summary>
- public interface IDiscountStrategy
- {
- decimal GetDiscount(goods g);
- }
具體策略角色
- /// <summary>
- /// 秋裝打折策略
- /// </summary>
- public class AutumnDressDiscountStrategy:IDiscountStrategy
- {
- #region IDiscountStrategy Members
- public decimal GetDiscount(goods g)
- {
- return (decimal)0.9 * g.Price;
- }
- #endregion
- }
- /// <summary>
- /// 運(yùn)動(dòng)鞋打折策略
- /// </summary>
- public class SportShoesDiscountStrategy:IDiscountStrategy
- {
- #region IDiscountStrategy Members
- public decimal GetDiscount(goods g)
- {
- return g.Price * (decimal)0.8;
- }
- #endregion
- }
購(gòu)物車
|
收銀臺(tái)角色
- /// <summary>
- /// 收銀臺(tái)
- /// </summary>
- public class CashierDesk
- {
- /// <summary>
- /// 購(gòu)物車
- /// </summary>
- private ShoppingCar shoppingCar;
- /// <summary>
- /// 策略字典
- /// </summary>
- private Dictionary<string, IDiscountStrategy> strategies;
- public CashierDesk(ShoppingCar sc, Dictionary<string, IDiscountStrategy> s)
- {
- this.shoppingCar = sc;
- this.strategies = s;
- }
- /// <summary>
- /// 獲得所有商品的價(jià)格
- /// </summary>
- /// <returns></returns>
- public decimal GetTotalPrice()
- {
- return shoppingCar.GoodsList.Sum(p => p.Price);
- }
- /// <summary>
- /// 獲得所有商品的總的折扣
- /// </summary>
- /// <returns></returns>
- public decimal GetTotalDiscount()
- {
- decimal sum = 0;
- IDiscountStrategy idiscountStrategy;
- foreach (goods g in shoppingCar.GoodsList)
- {
- idiscountStrategy=strategies.SingleOrDefault(p => p.Key == g.Type).Value;
- if (idiscountStrategy != null)
- {
- sum += idiscountStrategy.GetDiscount(g);
- }
- }
- return sum;
- }
- }
客戶端代碼
- class Client
- {
- static void Main(string[] args)
- {
- ShoppingCar sc = new ShoppingCar();
- Dictionary<string, IDiscountStrategy> discountD = new Dictionary<string, IDiscountStrategy>();
- //向購(gòu)物車中加入商品
- sc.AddGoods(new goods {Name="NIKE鞋 ",Price=100,Type="運(yùn)動(dòng)鞋" });
- sc.AddGoods(new goods { Name = "秋裝", Price = 200, Type = "秋裝" });
- sc.AddGoods(new goods { Name = "蘋(píng)果", Price = 300, Type = "水果" });
- //配置折扣策略
- discountD.Add("運(yùn)動(dòng)鞋", new SportShoesDiscountStrategy());
- discountD.Add("秋裝", new AutumnDressDiscountStrategy());
- CashierDesk cd = new CashierDesk(sc, discountD);
- //得到所有商品總價(jià)
- Console.WriteLine(cd.GetTotalPrice());
- //得到所有商品折扣價(jià)
- Console.WriteLine(cd.GetTotalDiscount());
- Console.ReadLine();
- }
- }
策略模式優(yōu)點(diǎn)與缺點(diǎn):
優(yōu)點(diǎn)
封裝了算法不穩(wěn)定性,易于以后業(yè)務(wù)策略的擴(kuò)展。
缺點(diǎn)
每種策略對(duì)應(yīng)于一個(gè)具體策略角色類,會(huì)增加系統(tǒng)需要維護(hù)的類的數(shù)量。
原文鏈接:http://www.cnblogs.com/ejiyuan/archive/2012/06/28/2567905.html
【編輯推薦】