行為型設(shè)計(jì)模式概述
行為模式關(guān)注的是對(duì)象的行為。該類(lèi)型的模式需要做的是對(duì)可能變化的行為進(jìn)行抽象,通過(guò)封裝達(dá)到整個(gè)架構(gòu)的可擴(kuò)展性。這些模式所要封裝的行為,恰恰是軟件架構(gòu)中最不穩(wěn)定的部分,擴(kuò)展的可能性也最大。將這些行為封裝起來(lái),利用抽象的特性,就提供了擴(kuò)展的可能。
實(shí)現(xiàn)的機(jī)制:
使用繼承機(jī)制在類(lèi)間分派行為
使用對(duì)象組合而不是繼承,它描述一組對(duì)象怎樣協(xié)作完成單個(gè)對(duì)象所無(wú)法完成的任務(wù)
(一)模板方法(TEMPLATE METHOD)模式
問(wèn)題提出:
通常我們會(huì)遇到這樣的一個(gè)問(wèn)題:我們知道一個(gè)算法或是流程所需的關(guān)鍵步聚,并確定了這些步聚的執(zhí)行順序。但是某些步聚的具體實(shí)現(xiàn)是未知的,或者是某些步聚的實(shí)現(xiàn)與具體的環(huán)境相關(guān)。
生活場(chǎng)景:
一個(gè)簡(jiǎn)單的訂單處理需求:一個(gè)客戶(hù)可以在一個(gè)訂貨單中訂購(gòu)多個(gè)貨物(也稱(chēng)為訂貨單項(xiàng)目),貨物的銷(xiāo)售價(jià)是根據(jù)貨物的進(jìn)貨價(jià)進(jìn)行計(jì)算的。有些貨物可以打折的,有些是不可以打折的。每一個(gè)客戶(hù)都有一個(gè)信用額度,每張訂單的總價(jià)不能超出該客戶(hù)的信用額度。
根據(jù)上面的業(yè)務(wù),我們可以知道處理一個(gè)訂單需要的步聚:
1. 遍歷訂貨單的訂貨單項(xiàng)目列表,累加所有貨物的總價(jià)格(根據(jù)訂貨單項(xiàng)目計(jì)算出銷(xiāo)售價(jià))
2. 根據(jù)客戶(hù)號(hào)獲得客戶(hù)的信用額度
3. 把客戶(hù)號(hào),訂單的總價(jià)格,及訂單項(xiàng)目列表寫(xiě)入到數(shù)據(jù)庫(kù)
但是我們并不能確定怎么計(jì)算出貨物的銷(xiāo)售價(jià),怎樣根據(jù)客戶(hù)號(hào)獲得客戶(hù)的信用額度及把訂單信息寫(xiě)入數(shù)據(jù)庫(kù)這些方法的具體實(shí)現(xiàn)。
模板方法模式把我們不知道具體實(shí)現(xiàn)的步聚封裝成抽象方法,提供一些按正確順序調(diào)用它們的具體方法(這些具體方法統(tǒng)稱(chēng)為模板方法),這樣構(gòu)成一個(gè)抽象基類(lèi)。子類(lèi)通過(guò)繼承這個(gè)抽象基類(lèi)去實(shí)現(xiàn)各個(gè)步聚的抽象方法,而工作流程卻由父類(lèi)來(lái)控制。
public abstract class AbstractOrder {
public Order placeOrder(int customerId , List orderItemList){
int total = 0;
for(int i = 0; i < orderItemList.size();i++){
OrderItem orderItem = (OrderItem)orderItemList.get(i);
total += getOrderItemPRice(orderItem) * orderItem.getQuantity();
}
if(total > getSpendingLimit(customerId)){
throw new BusinessException("超出信用額度" + getSpendingLimit(customerId));
}
int orderId = saveOrder(customerId, total, orderItemList);
return new OrderImpl(orderId,total);
}
public abstract int getOrderItemPrice(OrderItem orderItem);
public abstract int getSpendingLimit(int customerId);
public abstract int saveOrder(int customerId, int total, List orderItemList);
}
把不變的行為搬到超類(lèi),去除子類(lèi)中重復(fù)的代碼來(lái)體現(xiàn)他的優(yōu)勢(shì)。當(dāng)不變的和可變的行為在方法中混合在一起時(shí),不變的行為就會(huì)在子類(lèi)中重復(fù)出現(xiàn),模板方法模式就是將這些不變的行為搬移到一個(gè)超類(lèi)中,避免重復(fù)代碼。
這是一個(gè)很簡(jiǎn)單的模式,卻被非常廣泛的使用。之所以簡(jiǎn)單是因?yàn)樵谶@個(gè)模式中僅僅使用到了繼承關(guān)系。
繼承關(guān)系由于自身的缺陷,被專(zhuān)家們扣上了“罪惡”的帽子。“使用委派關(guān)(聚合)系代替繼承關(guān)系”,“盡量使用接口實(shí)現(xiàn)而不是抽象類(lèi)繼承”等等專(zhuān)家警告,讓我們這些菜鳥(niǎo)對(duì)繼承“另眼相看”。
其實(shí),繼承還是有很多自身的優(yōu)點(diǎn)所在。只是被大家濫用的似乎缺點(diǎn)更加明顯了。合理的利用繼承關(guān)系,還是能對(duì)你的系統(tǒng)設(shè)計(jì)起到很好的作用的。而模板方法模式就是其中的一個(gè)使用范例。
(二)策略(Strategy )模式
問(wèn)題提出:
在業(yè)務(wù)當(dāng)中常常出現(xiàn)一個(gè)問(wèn)題有一組算法,在不同的情況下我們有可能使用不同的算法。我們需要找到一種靈活簡(jiǎn)便的設(shè)計(jì)方式:將每一個(gè)算法封裝到具有共同接口的獨(dú)立的類(lèi)中,從而使得它們可以相互替換。
生活場(chǎng)景:
向客戶(hù)報(bào)價(jià),對(duì)于銷(xiāo)售部門(mén)的人來(lái)講,這是一個(gè)非常復(fù)雜的問(wèn)題,對(duì)不同的客戶(hù)要報(bào)不同的價(jià)格,比如:
對(duì)普通客戶(hù)或者是新客戶(hù)報(bào)的是全價(jià);
對(duì)老客戶(hù)報(bào)的價(jià)格,根據(jù)客戶(hù)年限,給予一定的折扣;
對(duì)大客戶(hù)報(bào)的價(jià)格,根據(jù)大客戶(hù)的累計(jì)消費(fèi)金額,給予一定的折扣;
還要考慮客戶(hù)購(gòu)買(mǎi)的數(shù)量和金額,比如:雖然是新用戶(hù),但是一次購(gòu)買(mǎi)的數(shù)量非常大,或者是總金額非常高,也會(huì)有一定的折扣;
還有,報(bào)價(jià)人員的職務(wù)高低,也決定了他是否有權(quán)限對(duì)價(jià)格進(jìn)行一定的浮動(dòng)折扣;
總之,向客戶(hù)報(bào)價(jià)是非常復(fù)雜的,不同的情況銷(xiāo)售人員采取不同的策略。
publicclassPrice {
/**@paramgoodsPrice商品銷(xiāo)售原價(jià)
*@paramcustomerType客戶(hù)類(lèi)型
*@return計(jì)算出來(lái)的,應(yīng)該給客戶(hù)報(bào)的價(jià)格*/
publicdoublequote(doublegoodsPrice,String customerType){
if(customerType.equals("普通客戶(hù)")){
System.out.println("對(duì)于新客戶(hù)或者是普通客戶(hù),沒(méi)有折扣");
returngoodsPrice;
}elseif(customerType.equals("老客戶(hù)")){
System.out.println("對(duì)于老客戶(hù),統(tǒng)一折扣5%");
returngoodsPrice*(1-0.05);
}elseif(customerType.equals("大客戶(hù)")){
System.out.println("對(duì)于大客戶(hù),統(tǒng)一折扣10%");
returngoodsPrice*(1-0.1);
}
returngoodsPrice;
}
}
價(jià)格類(lèi)包含了所有計(jì)算報(bào)價(jià)的算法,使得價(jià)格類(lèi),尤其是報(bào)價(jià)這個(gè)方法比較龐雜,難以維護(hù)。 咋辦?
publicclassPrice {
publicdoublequote(doublegoodsPrice,String customerType){
if(customerType.equals("普通客戶(hù)")){
returnthis.calcPriceForNormal(goodsPrice);
}elseif(customerType.equals("老客戶(hù)")){
returnthis.calcPriceForOld(goodsPrice);
}elseif(customerType.equals("大客戶(hù)")){
returnthis.calcPriceForLarge(goodsPrice);
}
returngoodsPrice;
}
privatedoublecalcPriceForNormal(doublegoodsPrice){…}
privatedoublecalcPriceForOld(doublegoodsPrice){…}
privatedoublecalcPriceForLarge(doublegoodsPrice){…}
}
再想想,問(wèn)題還是存在,只不過(guò)從計(jì)算報(bào)價(jià)的方法挪動(dòng)到價(jià)格類(lèi)里面了,假如有100個(gè)或者更多這樣的計(jì)算方式,這會(huì)讓這個(gè)價(jià)格類(lèi)非常龐大,難以維護(hù)。而且,維護(hù)和擴(kuò)展都需要去修改已有的代碼,這是很不好的,違反了開(kāi)-閉原則。
另外:經(jīng)常會(huì)有這樣的需要,在不同的時(shí)候,要使用不同的計(jì)算方式。
比如:在公司周年慶的時(shí)候,所有的客戶(hù)額外增加3%的折扣;在換季促銷(xiāo)的時(shí)候,普通客戶(hù)是額外增加折扣2%,老客戶(hù)是額外增加折扣3%,大客戶(hù)是額外增加折扣5%。這意味著計(jì)算報(bào)價(jià)的方式會(huì)經(jīng)常被修改,或者被切換。過(guò)了促銷(xiāo)時(shí)間,又還回到正常的價(jià)格體系上來(lái)了。而現(xiàn)在的價(jià)格類(lèi)中計(jì)算報(bào)價(jià)的方法,是固定調(diào)用各種計(jì)算方式,這使得切換調(diào)用不同的計(jì)算方式很麻煩,每次都需要修改if-else里面的調(diào)用代碼。
用來(lái)解決上述問(wèn)題的一個(gè)合理的解決方案就是策略模式。
策略模式使得算法可以在不影響到客戶(hù)端的情況下發(fā)生變化。策略模式把行為和環(huán)境分開(kāi)。環(huán)境類(lèi)負(fù)責(zé)維持和查詢(xún)行為類(lèi),各種算法在具體的策略類(lèi)中提供。由于算法和環(huán)境獨(dú)立開(kāi)來(lái),算法的增減,修改都不會(huì)影響到環(huán)境和客戶(hù)端。
根據(jù)策略模式解決場(chǎng)景問(wèn)題,設(shè)計(jì)如下:
public interface Baojia {public double baojia(double price);
}
public class Content {private Baojia bj;
public Content(Baojia bj) {super();this.bj = bj;}public double daZhe(double price){return bj.baojia(price);}
}
public class LaoKeHuYear3_5 implements Baojia {
@Overridepublic double baojia(double price) {return price*.75;}
}
public class NewKenHu implements Baojia {
@Overridepublic double baojia(double price) {if(price>10000){return price*(1-0.2);}return price;}
}
public class DaKeHu implements Baojia {
@Overridepublic double baojia(double price) {return price*0.5;}
}
public class Test {public static void main(String[] args) {Baojia bj = new LaoKeHuYear3_5();Content c = new Content(bj);double p = c.daZhe(1000);System.out.println(p);}
}
(三)觀察者(Observer )模式
問(wèn)題提出:
在軟件系統(tǒng)中,我們有時(shí)需要定義一種一對(duì)多的依賴(lài)關(guān)系. 讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽(tīng)某一個(gè)主題對(duì)象, 這個(gè)主題對(duì)象在狀態(tài)發(fā)生變化的時(shí)候,會(huì)通知所有的觀察者對(duì)象,使他們能夠自動(dòng)更新自己。
生活場(chǎng)景:
網(wǎng)上商店中商品在名稱(chēng) 價(jià)格等方面有變化,如果系統(tǒng)能自動(dòng)通知會(huì)員,將網(wǎng)上商店區(qū)別傳統(tǒng)商店的一大特色.
代碼實(shí)現(xiàn):定義主題類(lèi)
public class product extends Observable{
private String name;
private float price;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
//設(shè)置變化點(diǎn)
setChanged();
notifyObservers(name);
}
public float getPrice(){
return price;
}
public void setPrice(float price){
this.price=price;
//設(shè)置變化點(diǎn)
setChanged();
notifyObservers(new Float(price));
}
}
代碼實(shí)現(xiàn):定義觀察者
public class NameObserver implements Observer{
private String name=null;
public void update(Observable obj,Object arg){
if (arg instanceof String){
name=(String)arg;
System.out.println("NameObserver :name changet to "+name);
}
}
}
public class PriceObserver implements Observer{
private float price=0;
public void update(Observable obj,Object arg){
if (arg instanceof Float){
price=((Float)arg).floatValue();
System.out.println("PriceObserver :price changet to "+price);
}
}
}
public class Test {
public static void main(String args[]){
Product product=new Product();
NameObserver nameobs=new NameObserver();
PriceObserver priceobs=new PriceObserver();
//加入觀察者
product.addObserver(nameobs);
product.addObserver(priceobs);
product.setName("橘子紅了");
product.setPrice(9.22f);
}
}
(四)命令(Command )模式
問(wèn)題提出:
在軟件系統(tǒng)中,“行為請(qǐng)求者”與“行為實(shí)現(xiàn)者”通常呈現(xiàn)一種“緊耦合”。但在某些場(chǎng)合,比如要對(duì)行為進(jìn)行“記錄、撤銷(xiāo)/重做、事務(wù)”等處理,這種無(wú)法抵御變化的緊耦合是不合適的。在這種情況下,如何將“行為請(qǐng)求者”與“行為實(shí)現(xiàn)者”解耦?將一組行為抽象為對(duì)象,可以實(shí)現(xiàn)二者之間的松耦合。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注