定義了算法家族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化,不會影響到使用算法的客戶。
例子:商場收銀,營業員根據客戶所購買商品的單價和數量,向客戶收費,但有時候會有打折或者滿減活動,eg:打八折,滿300送100
先用上個學習到的簡單工廠實現一下
建立一個現金收費的抽象類
package celv;
abstract class CashSuper {
// 現金收取超類的抽象方法,收取現金,參數爲原價,返回僞當前價
public abstract double acceptCash(double money);
}
正常收費子類
package celv;
public class CashNormal extends CashSuper{
@Override
public double acceptCash(double money) {
// TODO Auto-generated method stub
return money;
}
}
折扣收費子類
package celv;
public class CashRebate extends CashSuper{
public double moneyRebate=1;
public CashRebate(String moneyRebate){
this.moneyRebate=Double.parseDouble(moneyRebate);
}
@Override
public double acceptCash(double money) {
// TODO Auto-generated method stub
return money*moneyRebate;
}
}
滿減收費子類
package celv;
public class CashReturn extends CashSuper{
public double moneyCondition=0.0;
public double moneyReturn=0.0;
public CashReturn(String moneyCondition,String moneyReturn){
this.moneyCondition=Double.parseDouble(moneyCondition);
this.moneyReturn=Double.parseDouble(moneyReturn);
}
@Override
public double acceptCash(double money) {
// TODO Auto-generated method stub
double result=money;
if(money>=moneyCondition){
result=money-Math.floor(money/moneyCondition)*moneyReturn;
}
return result;
}
}
一個工廠類來生成不同實例
package celv;
public class CashFactory {
public static CashSuper createCashAccept(String type){
CashSuper cs=null;
switch(type){
case "收費正常":
CashNormal cs0=new CashNormal();
cs=cs0;
break;
case "滿300返100":
CashReturn cr1=new CashReturn("300","100");
cs=cr1;
break;
case "打八折":
CashRebate cr2=new CashRebate("0.8");
cs=cr2;
break;
}
return cs;
}
}
測試
package celv;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
double price=50;
double number=20;
double total=0;
String type="滿300返100";
// 簡單工廠模式
CashSuper csuper=CashFactory.createCashAccept(type);
total=csuper.acceptCash(price*number);
System.out.println(total);
}
}
上述可以完成基本功能要求,但是,工廠本身包括了所有的收費方式,商場是可能經常性地更改打折額度和返利額度,每次維護或擴展收費方式都要改動這個工廠,以至於代碼需要重新編譯部署。
策略模式:商場收銀時如何促銷,打折還是返利,其實都是一些算法,用工廠來生成算法對象沒有錯,但算法本身只是一種策略,最重要的是這些算法是隨時都可能互相替換的,這就是變化點,封裝變化點是面向對象的一種很重要的思維方式。
於是,可以不用工廠,建立一個Context上下文,用一個ConcreteStrategy來配置,維護一個對Strategy對象的應用,即:
package celv;
public class CashContext {
public CashSuper cs;
// 通過構造方法,傳入具體的收費策略
public CashContext(CashSuper cs){
this.cs=cs;
}
// 根據收費策略的不同,獲得計算結果
public double getResult(double money){
return cs.acceptCash(money);
}
}
此時,客戶端的測試可以寫爲:
package celv;
public class celvTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
double price=50;
double number=20;
double total=0;
String type="滿300返100";
CashContext cc=null;
switch(type){
case "收費正常":
cc=new CashContext(new CashNormal());
break;
case "滿300返100":
cc=new CashContext(new CashReturn("300","100"));
break;
case "打八折":
cc=new CashContext(new CashRebate("0.8"));
break;
}
total=cc.getResult(price*number);
System.out.println(total);
}
}
但是也存在缺點,即讓客戶端去判斷用哪一個算法,可以試着將簡單工工廠和策略模式進行結合,將CashContext類修改
package celv;
public class CashContext {
public CashSuper cs;
// 通過構造方法,傳入具體的收費策略
public CashContext(CashSuper cs){
this.cs=cs;
}
public CashContext(String type){
switch(type){
case "收費正常":
CashNormal cs0=new CashNormal();
cs=cs0;
break;
case "滿300返100":
CashReturn cr1=new CashReturn("300","100");
cs=cr1;
break;
case "打八折":
CashRebate cr2=new CashRebate("0.8");
cs=cr2;
break;
}
}
// 根據收費策略的不同,獲得計算結果
public double getResult(double money){
return cs.acceptCash(money);
}
}
那麼,客戶端代碼就是這樣子
package celv;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
double price=50;
double number=20;
double total=0;
String type="滿300返100";
// 策略模式與簡單工廠結合
CashContext csuper=new CashContext(type);
total=csuper.getResult(price*number);
System.out.println(total);
}
}
由此可以看出,簡單工廠模式讓客戶端認識兩個類,即CashSuper和CashFactory,而策略模式與簡單工廠結合,客戶端只需要認識一個類CashContext,耦合度更低。
總結:策略模式是一種定義一系列算法的方法,從概念上看,所有這些算法完成的都是相同的工作,只是實現不同,它可以以相同的方式調用所有的算法,減少了各種算法類與使用算法類之間的耦合。簡化了單元測試,因爲每個算法都有自己的類,可以通過自己的接口單獨測試。
參考自《大話設計模式》,代碼自己有所修改,持續更新。