假設如今要設計一個麥各種書籍的電子商務汪涵的(Shoping Card)系統,一個最簡單的狀況就是把全部貨品的單價乘上數量,可是實際狀況確定要比這複雜。好比本網站可能對全部的教材類圖書實行每本兩元的折扣;對連環畫類圖書提供每本10%的促銷折扣,而非教材類的計算機圖書有5%的折扣;對其他書沒有折扣。因爲有這樣複雜的折扣算法,使得價格計算問題須要系統地解決。html
那麼怎麼樣才能解決這個問題呢?java
其實,解決方法不止一種,例如咱們能夠把全部邏輯放在客戶端利用條件語句判斷決定使用哪種算法;也能夠利用繼承在子類裏面實現不一樣打折算法;還能夠利用策略模式將環境和各類算法分開,將具體實現與客戶端解耦。算法
實現這個策略的UML圖以下:dom
抽象策略類(DiscountStrategy)測試
package com.strategy.booksale; /** * 抽象策略類,定義了抽象算法 * @author LLS * */ abstract public class DiscountStrategy { //抽象方法 abstract public double calculateDiscount(); }
10%的折扣促銷類(PercentageStrategy)網站
package com.strategy.booksale; /** * 折扣銷售圖書類 * @author LLS * */ public class PercentageStrategy extends DiscountStrategy { //保存單價、數量、總額 private double percent = 0.0; private double price = 0.0; private int copies = 0; public PercentageStrategy(double price, int copies,double percent) { this.percent=percent; this.price = price; this.copies = copies; } public double getPercent() { return percent; } public void setPercent(double percent) { this.percent = percent; } //覆蓋父類的抽象方法 public double calculateDiscount() { return copies * price * percent; } }
平價打折類(FlatRateStrategy)this
package com.strategy.booksale; /** * 平價銷售圖書,不進行打折算法類 * @author LLS * */ public class FlatRateStrategy extends DiscountStrategy { //保存圖書單價、數量、總額 private double amount; private double price = 0; private int copies = 0; public FlatRateStrategy(double price, int copies) { this.price = price; this.copies = copies; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } //覆蓋了抽象類的方法 public double calculateDiscount() { return copies * amount; } }
不打折類(NoDiscountStrategy) spa
package com.strategy.booksale;
public class NoDiscountStrategy extends DiscountStrategy
{
private double price = 0.0;
private int copies = 0;
public NoDiscountStrategy(double price, int copies)
{
this.price = price;
this.copies = copies;
}
public double calculateDiscount()
{
return price*copies;
}
}
維護抽象類的引用設計
package com.strategy.booksale;
/**
* 維護抽象策略類的引用
* @author LLS
*
*/
public class Context {
//維護策略抽象類的一個引用
DiscountStrategy discountStrategy;
//傳入具體策略對象
public Context(DiscountStrategy discountStrategy)
{
this.discountStrategy=discountStrategy;
}
//根據具體策略對象調用其方法
public void ContextInterface()
{
discountStrategy.calculateDiscount();
}
}
客戶端測試類(Test)code
package com.strategy.booksale; public class Test { public static void main(String[] args) { //維護抽象策略類 Context context; //採用平價打折,單價爲10元,數量5本 context=new Context(new FlatRateStrategy(10, 5)); //採用百分比打折10% context=new Context(new PercentageStrategy(10, 5,0.1)); } }
這樣利用策略模式已經解決了多種打折的問題,可是你有沒有發現策略只是實現了在不一樣打折方法或不一樣算法行爲之間得靈活切換,並無控制實例化哪個算法,須要用哪個優惠方式是由客戶端決定的,因此客戶端與具體的實現類之間耦合性很大,還須要進一步解耦。
策略模式更注重於n選1的狀況,這也就是說若是我想組合幾種不一樣的打折策略,策略就會顯得不恰當,由於你須要將多個打折方法都寫到一個類裏面去,爲解決這種狀況,能夠配合裝飾(Decorator)模式一塊兒使用。
裝飾模式適合給一個類添加額外的職責,而且對客戶端透明。
咱們來看一張表示裝飾模式的圖,這張圖即代表了它的添加功能特性也它的透明性。
你們很容器想到簡單工廠,它就是一個封裝產生對象的過程的類,經過傳入字符串的方式決定實例化哪個類,可是它也有不足,若是咱們須要加入新的打折策略時,就須要改動工廠裏面的代碼,這違反了OCP原則。
咱們能夠利用反射來動態決定實例化哪一個策略,配置文件和反射類以下:
配置文件設置
<?xml version="1.0" encoding="UTF-8"?>
<config>
<!-- 標籤 -->
<strategy-class>
<!-- 折扣策略類 -->
<strategy id="com.strategy.booksale.DiscountStrategy" class="com.strategy.booksale.PercentateStrategy"></strategy>
</strategy-class>
</config>
反射類
package com.strategy.booksale;
import java.util.HashMap;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* 反射類
* @author LLS
*
*/
public class Reflection{
//採用餓漢式單利模式,可能佔內存
private static Reflection instance=new Reflection();
//系統缺省配置文件名稱
private final String sysconfig="sys-config.xml";
//保存具體策略鍵值對
private Map strategyMap =new HashMap();
//讀取出來的document對象
private Document doc;
private Reflection()
{
try {
doc=new SAXReader().read(Thread.currentThread().getContextClassLoader().getResourceAsStream(beansConfigFile));
} catch (Exception e) {
e.printStackTrace();
}
}
//獲得實例 的方法
public static Reflection getInstance() {
return instance;
}
/**
* 根據策略 編號,取得的具體策略
* @param beanId
* @return
*/
public synchronized Object getStrategyObject(Class c)
{
//判斷serviceMap裏面是否有service對象,沒有建立,有返回
if (strategyMap.containsKey(c.getName())) {
return strategyMap.get(c.getName());
}
//返回指定ID的Element對象
Element strategyElement=(Element)doc.selectSingleNode("//strategy[@id=\""+c.getName()+"\"]");
String className=strategyElement.attributeValue("class");
Object strategyObject=null;
try {
strategyObject = Class.forName(className).newInstance();
strategyMap.put(c.getName(), strategyObject);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return strategyObject;
}
}
改寫後的客戶端以下:
package com.strategy.booksale;
public class Test {
public static void main(String[] args)
{
//維護抽象策略類
Context context;
//利用反射動態決定使用哪種打折策略
DiscountStrategy discountStrategy=(DiscountStrategy)Reflection.getInstance().getStrategyObject(DiscountStrategy.class);
//客戶端只須要識別策略抽象類便可,與具體的算法解耦
context=new Context(discountStrategy);
}
}
這樣一來客戶端徹底不知道有什麼算法,也不知道該實例化哪個,減小了客戶端的職責。
最後,咱們用裝飾模式來解決策略不能夠組合多個打折方式的不足,裝飾模式的主要做用便可以給一個對象動態添加多種功能,下面是我畫的類圖,有了類圖代碼能夠本身實現,讓它們共同實現了同一個抽象類。
左邊部分是裝飾模式負責動態組合各類打折方法,右邊是策略模式動態選擇其中一種打折策略,之因此它們的功能能夠一塊兒使用,這裏是由於他們實現了一個共同的接口 (Interface)。
有些問題若是咱們站在接口或抽象類的角度去考慮,用接口和抽象的方式去思考,有時問題會容易解決一些,而不要一直在某個具體的類範圍內考慮問題,即把思考的角度範圍擴展,從高層次考慮更容容易解決下面層次的問題。
思想上移一些,站在這個問題的更高一層。