向客戶報價,對於銷售部門的人來說,這是一個很是重大、很是複雜的問題,對不一樣的客戶要報不一樣的價格,好比:html
對普通客戶或者是新客戶報的是全價java
對老客戶報的價格,根據客戶年限,給予必定的折扣算法
對大客戶報的價格,根據大客戶的累計消費金額,給予必定的折扣數據庫
還要考慮客戶購買的數量和金額,好比:雖然是新用戶,可是一次購買的數量很是大,或者是總金額很是高,也會有必定的折扣設計模式
還有,報價人員的職務高低,也決定了他是否有權限對價格進行必定的浮動折扣ide
甚至在不一樣的階段,對客戶的報價也不一樣,通常狀況是剛開始比較高,越接近成交階段,報價越趨於合理。
總之,向客戶報價是很是複雜的,所以在一些CRM(客戶關係管理)的系統中,會有一個單獨的報價管理模塊,來處理複雜的報價功能。
爲了演示的簡潔性,假定如今須要實現一個簡化的報價管理,實現以下的功能:
(1)對普通客戶或者是新客戶報全價
(2)對老客戶報的價格,統一折扣5%
(3)對大客戶報的價格,統一折扣10%
該怎麼實現呢?測試
要實現對不一樣的人員報不一樣的價格的功能,無外乎就是判斷起來麻煩點,也很少難,很快就有朋友能寫出以下的實現代碼,示例代碼以下:this
/** * 價格管理,主要完成計算向客戶所報價格的功能 */ public class Price { /** * 報價,對不一樣類型的,計算不一樣的價格 * @param goodsPrice 商品銷售原價 * @param customerType 客戶類型 * @return 計算出來的,應該給客戶報的價格 */ public double quote(double goodsPrice,String customerType){ if(customerType.equals("普通客戶 ")){ System.out.println("對於新客戶或者是普通客戶,沒有折扣 "); return goodsPrice; }else if(customerType.equals("老客戶 ")){ System.out.println("對於老客戶,統一折扣 5%"); return goodsPrice*(1-0.05); }else if(customerType.equals("大客戶 ")){ System.out.println("對於大客戶,統一折扣 10%"); return goodsPrice*(1-0.1); } //其他人員都是報原價 return goodsPrice; } }
上面的寫法是很簡單的,也很容易想,可是仔細想一想,這樣實現,問題可不小,好比:spa
第一個問題:價格類包含了全部計算報價的算法,使得價格類,尤爲是報價這個方法比較龐雜,難以維護。設計
有朋友可能會想,這很簡單嘛,把這些算法從報價方法裏面拿出去,造成獨 立的方法不就能夠解決這個問題了嗎?據此寫出以下的實現代碼,示例代碼以下:
/** * 價格管理,主要完成計算向客戶所報價格的功能 */ public class Price { /** * 報價,對不一樣類型的,計算不一樣的價格 * @param goodsPrice 商品銷售原價 * @param customerType 客戶類型 * @return 計算出來的,應該給客戶報的價格 */ public double quote(double goodsPrice,String customerType){ if(customerType.equals("普通客戶 ")){ return this.calcPriceForNormal(goodsPrice); }else if(customerType.equals("老客戶 ")){ return this.calcPriceForOld(goodsPrice); }else if(customerType.equals("大客戶 ")){ return this.calcPriceForLarge(goodsPrice); } //其他人員都是報原價 return goodsPrice; } /** * 爲新客戶或者是普通客戶計算應報的價格 * @param goodsPrice 商品銷售原價 * @return 計算出來的,應該給客戶報的價格 */ private double calcPriceForNormal(double goodsPrice){ System.out.println("對於新客戶或者是普通客戶,沒有折扣 "); return goodsPrice; } /** * 爲老客戶計算應報的價格 * @param goodsPrice 商品銷售原價 * @return 計算出來的,應該給客戶報的價格 */ private double calcPriceForOld(double goodsPrice){ System.out.println("對於老客戶,統一折扣 5%"); return goodsPrice*(1-0.05); } /** * 爲大客戶計算應報的價格 * @param goodsPrice 商品銷售原價 * @return 計算出來的,應該給客戶報的價格 */ private double calcPriceForLarge(double goodsPrice){ System.out.println("對於大客戶,統一折扣 10%"); return goodsPrice*(1-0.1); } }
這樣看起來,比剛開始稍稍好點,計算報價的方法會稍稍簡單一點,這樣維護起來也稍好一些,某個算法發生了變化,直接修改相應的私有方法就能夠了。擴展起來也容易一點,好比要增長一個「戰略合做客戶」的類型,報價爲直接8折,就只須要在價格類裏面新增長一個私有的方法來計算新的價格,而後在計算報價的方法裏面新添一個else-if便可。看起來彷佛很不錯了。
真的很不錯了嗎?
再想一想,問題仍是存在,只不過從計算報價的方法挪動到價格類裏面了,假若有100個或者更多這樣的計算方式,這會讓這個價格類很是龐大,難以維護。並且,維護和擴展都須要去修改已有的代碼,這是很很差的,違反了開-閉原則。
第二個問題:常常會有這樣的須要,在不一樣的時候,要使用不一樣的計算方式。
好比:在公司週年慶的時候,全部的客戶額外增長3%的折扣;在換季促銷的時候,普通客戶是額外增長折扣2%,老客戶是額外增長折扣3%,大客戶是額外增長折扣5%。這意味着計算報價的方式會常常被修改,或者被切換。
一般狀況下應該是被切換,由於過了促銷時間,又還回到正常的價格體系上來了。而如今的價格類中計算報價的方法,是固定調用各類計算方式,這使得切換調用不一樣的計算方式很麻煩,每次都須要修改if-else裏面的調用代碼。
看到這裏,可能有朋友會想, 那麼到底應該如何實現,纔可以讓價格類中的計算報價的算法,能很容易的實現可維護、可擴展,又能動態的切換變化呢?
用來解決上述問題的一個合理的解決方案就是策略模式。那麼什麼是策略模式呢?
(1)策略模式定義
定義一系列的算法,把它們一個個封裝起來,而且使它們可相互替換。本模式使得算法可獨 立於使用它的客戶而變化。
(2)應用策略模式來解決的思路
仔細分析上面的問題,先來把它抽象一下,各類計算報價的計算方式就比如是具體的算法,而使用這些計算方式來計算報價的程序,就至關因而使用算法的客戶。
再分析上面的實現方式,爲何會形成那些問題,根本緣由,就在於算法和使用算法的客戶是耦合的,甚至是密不可分的,在上面實現中,具體的算法和使用算法的客戶是同一個類裏面的不一樣方法。
如今要解決那些問題,按照策略模式的方式,應該先把全部的計算方式獨 立出來,每一個計算方式作成一個單獨的算法類,從而造成一系列的算法,而且爲這一系列算法定義一個公共的接口,這些算法實現是同一接口的不一樣實現,地位是平等的,能夠相互替換。這樣一來,要擴展新的算法就變成了增長一個新的算法實現類,要維護某個算法,也只是修改某個具體的算法實現便可,不會對其它代碼形成影響。也就是說這樣就解決了可維護、可擴展的問題。
爲了實現讓算法能獨 立於使用它的客戶,策略模式引入了一個上下文的對象,這個對象負責持有算法,可是不負責決定具體選用哪一個算法,把選擇算法的功能交給了客戶,由客戶選擇好具體的算法後,設置到上下文對象裏面,讓上下文對象持有客戶選擇的算法,當客戶通知上下文對象執行功能的時候,上下文對象會去轉調具體的算法。這樣一來,具體的算法和直接使用算法的客戶是分離的。
具體的算法和使用它的客戶分離事後,使得算法可獨 立於使用它的客戶而變化,而且可以動態的切換須要使用的算法,只要客戶端動態的選擇使用不一樣的算法,而後設置到上下文對象中去,實際調用的時候,就能夠調用到不一樣的算法。
Strategy:
策略接口,用來約束一系列具體的策略算法。Context使用這個接口來調用具體的策略實現定義的算法。
ConcreteStrategy:
具體的策略實現,也就是具體的算法實現。
Context:
上下文,負責和具體的策略類交互,一般上下文會持有一個真正的策略實現,上下文還可讓具體的策略類來獲取上下文的數據,甚至讓具體的策略類來回調上下文的方法。
(1)首先來看策略,也就是定義算法的接口,示例代碼以下:
/** * 策略,定義算法的接口 */ public interface Strategy { /** * 某個算法的接口,能夠有傳入參數,也能夠有返回值 */ public void algorithmInterface(); }
(2)該來看看具體的算法實現了,定義了三個,分別是ConcreteStrategyA、ConcreteStrategyB、ConcreteStrategyC,示例很是簡單,因爲沒有具體算法的實現,三者也就是名稱不一樣,示例代碼以下:
/** * 實現具體的算法 */ public class ConcreteStrategyA implements Strategy { public void algorithmInterface() { //具體的算法實現 } }
/** * 實現具體的算法 */ public class ConcreteStrategyB implements Strategy { public void algorithmInterface() { //具體的算法實現 } }
/** * 實現具體的算法 */ public class ConcreteStrategyC implements Strategy { public void algorithmInterface() { //具體的算法實現 } }
(3)再來看看上下文的實現,示例代碼以下:
/** * 上下文對象,一般會持有一個具體的策略對象 */ public class Context { /** * 持有一個具體的策略對象 */ private Strategy strategy; /** * 構造方法,傳入一個具體的策略對象 * @param aStrategy 具體的策略對象 */ public Context(Strategy aStrategy) { this.strategy = aStrategy; } /** * 上下文對客戶端提供的操做接口,能夠有參數和返回值 */ public void contextInterface() { //一般會轉調具體的策略對象進行算法運算 strategy.algorithmInterface(); } }
要使用策略模式來重寫前面報價的示例,大體有以下改變:
首先須要定義出算法的接口。
而後把各類報價的計算方式單獨出來,造成算法類。
對於Price這個類,把它當作上下文,在計算報價的時候,再也不須要判斷,直接使用持有的具體算法進行運算便可。選擇使用哪個算法的功能挪出去,放到外部使用的客戶端去。
(1)先看策略接口,示例代碼以下:
/** * 策略,定義計算報價算法的接口 */ public interface Strategy { /** * 計算應報的價格 * @param goodsPrice 商品銷售原價 * @return 計算出來的,應該給客戶報的價格 */ public double calcPrice(double goodsPrice); }
(2)接下來看看具體的算法實現,不一樣的算法,實現也不同,先看爲新客戶或者是普通客戶計算應報的價格的實現,示例代碼以下:
/** * 具體算法實現,爲新客戶或者是普通客戶計算應報的價格 */ public class NormalCustomerStrategy implements Strategy{ public double calcPrice(double goodsPrice) { System.out.println("對於新客戶或者是普通客戶,沒有折扣"); return goodsPrice; } }
再看看爲老客戶計算應報的價格的實現,示例代碼以下:
/** * 具體算法實現,爲老客戶計算應報的價格 */ public class OldCustomerStrategy implements Strategy{ public double calcPrice(double goodsPrice) { System.out.println("對於老客戶,統一折扣5%"); return goodsPrice*(1-0.05); } }
再看看爲大客戶計算應報的價格的實現,示例代碼以下:
/** * 具體算法實現,爲大客戶計算應報的價格 */ public class LargeCustomerStrategy implements Strategy{ public double calcPrice(double goodsPrice) { System.out.println("對於大客戶,統一折扣10%"); return goodsPrice*(1-0.1); } }
(3)接下來看看上下文的實現,也就是原來的價格類,它的變化比較大,主要有:
原來那些私有的,用來作不一樣計算的方法,已經去掉了,獨 立出去作成了算法類
原來報價方法裏面,對具體計算方式的判斷,去掉了,讓客戶端來完成選擇具體算法的功能
新添加持有一個具體的算法實現,經過構造方法傳入
原來報價方法的實現,變化成了轉調具體算法來實現
示例代碼以下:
/** * 價格管理,主要完成計算向客戶所報價格的功能 */ public class Price { /** * 持有一個具體的策略對象 */ private Strategy strategy = null; /** * 構造方法,傳入一個具體的策略對象 * @param aStrategy 具體的策略對象 */ public Price(Strategy aStrategy){ this.strategy = aStrategy; } /** * 報價,計算對客戶的報價 * @param goodsPrice 商品銷售原價 * @return 計算出來的,應該給客戶報的價格 */ public double quote(double goodsPrice){ return this.strategy.calcPrice(goodsPrice); } }
(4)寫個客戶端來測試運行一下,好加深體會,示例代碼以下:
public class Client { public static void main(String[] args) { //1:選擇並建立須要使用的策略對象 Strategy strategy = new LargeCustomerStrategy (); //2:建立上下文 Price ctx = new Price(strategy); //3:計算報價 double quote = ctx.quote(1000); System.out.println("向客戶報價:"+quote); } }
運行一下,看看效果。
你能夠修改使用不一樣的策略算法具體實現,如今用的是LargeCustomerStrategy,你能夠嘗試修改爲其它兩種實現,試試看,體會一下切換算法的容易性。
容錯恢復機制是應用程序開發中很是常見的功能。那麼什麼是容錯恢復呢?簡單點說就是:程序運行的時候,正常狀況下應該按照某種方式來作,若是按照某種方式來作發生錯誤的話,系統並不會崩潰,也不會就此不能繼續向下運行了,而是有容忍出錯的能力,不但能容忍程序運行出現錯誤,還提供出現錯誤後的備用方案,也就是恢復機制,來代替正常執行的功能,使程序繼續向下運行。
舉個實際點的例子吧,好比在一個系統中,全部對系統的操做都要有日誌記錄,並且這個日誌還須要有管理界面,這種狀況下一般會把日誌記錄在數據庫裏面,方便後續的管理,可是在記錄日誌到數據庫的時候,可能會發生錯誤,好比暫時連不上數據庫了,那就先記錄在文件裏面,而後在合適的時候把文件中的記錄再轉錄到數據庫中。
對於這樣的功能的設計,就能夠採用策略模式,把日誌記錄到數據庫和日誌記錄到文件看成兩種記錄日誌的策略,而後在運行期間根據須要進行動態的切換。
在這個例子的實現中,要示範由上下文來選擇具體的策略算法,前面的例子都是由客戶端選擇好具體的算法,而後設置到上下文中。
下面仍是經過代碼來示例一下。
(1)先定義日誌策略接口,很簡單,就是一個記錄日誌的方法,示例代碼以下:
/** * 日誌記錄策略的接口 */ public interface LogStrategy { /** * 記錄日誌 * @param msg 需記錄的日誌信息 */ public void log(String msg); }
(2)實現日誌策略接口,先實現默認的數據庫實現,假設若是日誌的長度超過長度就出錯,製造錯誤的是一個最多見的運行期錯誤,示例代碼以下:
/** * 把日誌記錄到數據庫 */ public class DbLog implements LogStrategy{ public void log(String msg) { //製造錯誤 if(msg!=null && msg.trim().length()>5){ int a = 5/0; } System.out.println("如今把 '"+msg+"' 記錄到數據庫中"); } }
接下來實現記錄日誌到文件中去,示例代碼以下:
/** * 把日誌記錄到文件 */ public class FileLog implements LogStrategy{ public void log(String msg) { System.out.println("如今把 '"+msg+"' 記錄到文件中"); } }
(3)接下來定義使用這些策略的上下文,注意此次是在上下文裏面實現具體策略算法的選擇,因此不須要客戶端來指定具體的策略算法了,示例代碼以下:
(4)看看如今的客戶端,沒有了選擇具體實現策略算法的工做,變得很是簡單,故意多調用一次,能夠看出不一樣的效果,示例代碼以下:
(5)小結一下,經過上面的示例,會看到策略模式的一種簡單應用,也順便了解一下基本的容錯恢復機制的設計和實現。在實際的應用中,須要設計容錯恢復的系統通常要求都比較高,應用也會比較複雜,可是基本的思路是差很少的。
在實際應用策略模式的過程當中,常常會出現這樣一種狀況,就是發現這一系列算法的實現上存在公共功能,甚至這一系列算法的實現步驟都是同樣的,只是在某些局部步驟上有所不一樣,這個時候,就須要對策略模式進行些許的變化使用了。
對於一系列算法的實現上存在公共功能的狀況,策略模式能夠有以下三種實現方式:
一個是在上下文當中實現公共功能,讓全部具體的策略算法回調這些方法。
另一種狀況就是把策略的接口改爲抽象類,而後在裏面實現具體算法的公共功能。
還有一種狀況是給全部的策略算法定義一個抽象的父類,讓這個父類去實現策略的接口,而後在這個父類裏面去實現公共的功能。
更進一步,若是這個時候發現「一系列算法的實現步驟都是同樣的,只是在某些局部步驟上有所不一樣」的狀況,那就能夠在這個抽象類裏面定義算法實現的骨架,而後讓具體的策略算法去實現變化的部分。這樣的一個結構天然就變成了策略模式來結合模板方法模式了,那個抽象類就成了模板方法模式的模板類。
在上一章咱們討論過模板方法模式來結合策略模式的方式,也就是主要的結構是模板方法模式,局部採用策略模式。而這裏討論的是策略模式來結合模板方法模式,也就是主要的結構是策略模式,局部實現上採用模板方法模式。經過這個示例也能夠看出來,模式之間的結合是沒有定勢的,要具體問題具體分析。
此時策略模式結合模板方法模式的系統結構以下圖所示:
仍是用實際的例子來講吧,好比上面那個記錄日誌的例子,若是如今須要在全部的消息前面都添加上日誌時間,也就是說如今記錄日誌的步驟變成了:第一步爲日誌消息添加日誌時間;第二步具體記錄日誌。
那麼該怎麼實現呢?
(1)記錄日誌的策略接口沒有變化,爲了看起來方便,仍是示例一下,示例代碼以下:
/** * 日誌記錄策略的接口 */ public interface LogStrategy { /** * 記錄日誌 * @param msg 需記錄的日誌信息 */ public void log(String msg); }
(2)增長一個實現這個策略接口的抽象類,在裏面定義記錄日誌的算法骨架,至關於模板方法模式的模板,示例代碼以下:
/** * 實現日誌策略的抽象模板,實現給消息添加時間 */ public abstract class LogStrategyTemplate implements LogStrategy{ public final void log(String msg) { //第一步:給消息添加記錄日誌的時間 DateFormat df = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss SSS"); msg = df.format(new java.util.Date())+" 內容是:"+ msg; //第二步:真正執行日誌記錄 doLog(msg); } /** * 真正執行日誌記錄,讓子類去具體實現 * @param msg 需記錄的日誌信息 */ protected abstract void doLog(String msg); }
(3)這個時候那兩個具體的日誌算法實現也須要作些改變,再也不直接實現策略接口了,而是繼承模板,實現模板方法了。這個時候記錄日誌到數據庫的類,示例代碼以下:
/** * 把日誌記錄到數據庫 */ public class DbLog extends LogStrategyTemplate{ //除了定義上發生了改變外,具體的實現沒變 public void doLog(String msg) { //製造錯誤 if(msg!=null && msg.trim().length()>5){ int a = 5/0; } System.out.println("如今把 '"+msg+"' 記錄到數據庫中"); } }
同理實現記錄日誌到文件的類以下:
/** * 把日誌記錄到數據庫 */ public class FileLog extends LogStrategyTemplate{ public void doLog(String msg) { System.out.println("如今把 '"+msg+"' 記錄到文件中"); } }
(4)算法實現的改變不影響使用算法的上下文,上下文跟前面同樣,示例代碼以下:
/** * 日誌記錄的上下文 */ public class LogContext { /** * 記錄日誌的方法,提供給客戶端使用 * @param msg 需記錄的日誌信息 */ public void log(String msg){ //在上下文裏面,自行實現對具體策略的選擇 //優先選用策略:記錄到數據庫 LogStrategy strategy = new DbLog(); try{ strategy.log(msg); }catch(Exception err){ //出錯了,那就記錄到文件中 strategy = new FileLog(); strategy.log(msg); } } }
(5)客戶端跟之前也同樣,示例代碼以下:
public class Client { public static void main(String[] args) { LogContext log = new LogContext(); log.log("記錄日誌"); log.log("再次記錄日誌"); } }
運行一下客戶端再次測試看看,體會一下,看看結果是否帶上了時間。
經過這個示例,好好體會一下策略模式和模板方法模式的組合使用,在實用開發中是很常見的方式。
策略模式和狀態模式
這兩個模式從模式結構上看是同樣的,可是實現的功能是不同的。
狀態模式是根據狀態的變化來選擇相應的行爲,不一樣的狀態對應不一樣的類,每一個狀態對應的類實現了該狀態對應的功能,在實現功能的同時,還會維護狀態數據的變化。這些實現狀態對應的功能的類之間是不能相互替換的。
策略模式是根據須要或者是客戶端的要求來選擇相應的實現類,各個實現類是平等的,是能夠相互替換的。
另外策略模式可讓客戶端來選擇須要使用的策略算法,而狀態模式通常是由上下文,或者是在狀態實現類裏面來維護具體的狀態數據,一般不禁客戶端來指定狀態。
策略模式和模板方法模式
這兩個模式可組合使用,如同前面示例的那樣。
模板方法重在封裝算法骨架,而策略模式重在分離並封裝算法實現。
策略模式和享元模式
這兩個模式可組合使用。
策略模式分離並封裝出一系列的策略算法對象,這些對象的功能一般都比較單一,不少時候就是爲了實現某個算法的功能而存在,所以,針對這一系列的、多個細粒度的對象,能夠應用享元模式來節省資源,但前提是這些算法對象要被頻繁的使用,若是偶爾用一次,就沒有必要作成享元了。
轉載至:http://sishuok.com/forum/blogPost/list/95.html
cc老師的設計模式是我目前看過最詳細最有實踐的教程。