https://blog.csdn.net/u012124438/article/details/70039943jps
寫代碼時總會出很多的if…else吆豹,或者case。如果在一個條件語句中又包含了多個條件語句就會使得代碼變得臃腫,維護的成本也會加大冠绢,而策略模式就能較好的解決這個問題囤躁,本篇博客就帶你詳細了解策略模式。
定義:策略模式定義了一系列的算法福铅,并將每一個算法封裝起來萝毛,而且使他們可以相互替換,讓算法獨立于使用它的客戶而獨立變化滑黔。
分析下定義笆包,策略模式定義和封裝了一系列的算法环揽,它們是可以相互替換的,也就是說它們具有共性庵佣,而它們的共性就體現(xiàn)在策略接口的行為上歉胶,另外為了達到最后一句話的目的,也就是說讓算法獨立于使用它的客戶而獨立變化秧了,我們需要讓客戶端依賴于策略接口跨扮。
策略模式的使用場景:
1.針對同一類型問題的多種處理方式,僅僅是具體行為有差別時验毡;?
2.需要安全地封裝多種同一類型的操作時衡创;?
3.出現(xiàn)同一抽象類有多個子類,而又需要使用 if-else 或者 switch-case 來選擇具體子類時晶通。
UML類圖
這個模式涉及到三個角色:
環(huán)境(Context)角色:持有一個Strategy的引用璃氢。
抽象策略(Strategy)角色:這是一個抽象角色,通常由一個接口或抽象類實現(xiàn)狮辽。此角色給出所有的具體策略類所需的接口一也。
具體策略(ConcreteStrategy)角色:包裝了相關的算法或行為。
策略模式的典型代碼如下:
抽象策略類
public interfaceStrategy{
?/**
? ? * 策略方法
? ? */? ? public void strategyInterface();
}
具體策略類
public class ConcreteStrategyA implements Strategy?
{
?@Override?
?public void strategyInterface() {
? ? ? ? //相關的業(yè)務
? ? }
}
public class ConcreteStrategyB implements Strategy
{?
?@Override
?public void strategyInterface() {
? ? ? ? //相關的業(yè)務??
? }
}
環(huán)境角色類
public classContext{?
?//持有一個具體策略的對象?
?private Strategy strategy;
? ? /**? ? * 構造函數(shù)喉脖,傳入一個具體策略對象? ? * @paramstrategy? ? 具體策略對象? ? */? ?
?public Context(Strategy strategy){
? ? ? ? this.strategy = strategy;
? ? }
? ? /**
? ? * 策略方法
? ? */? ?
?public void contextInterface(){
? ? ? ? strategy.strategyInterface();
? ? }
}
策略模式例子
假設鵝廠推出了3種會員椰苟,分別為會員,超級會員以及金牌會員树叽,還有就是普通玩家舆蝴,針對不同類別的玩家,購買《王者農(nóng)藥》皮膚有不同的打折方式题诵,并且一個顧客每消費10000就增加一個級別洁仗,那么我們就可以使用策略模式,因為策略模式描述的就是算法的不同性锭,這里我們舉例就采用最簡單的赠潦,以上四種玩家分別采用原價(普通玩家),九折草冈,八折和七價的收錢方式她奥。
那么我們首先要有一個計算價格的策略接口
public interface CalPrice {
? ? //根據(jù)原價返回一個最終的價格? ? Double calPrice(Double orgnicPrice);
}下面是4種玩家的計算方式的實現(xiàn)
public classOrgnicimplementsCalPrice{?
?@Override
?public Double calPrice(Double orgnicPrice) {
? ? ? ? return orgnicPrice;
? ? }
}
public classVipimplementsCalPrice{
?@Override?
?public Double calPrice(Double orgnicPrice) {
? ? ? ? return orgnicPrice * 0.9;
? ? }
}
public class SuperVip implements CalPrice{?
?@Override
?public Double calPrice(Double orgnicPrice) {
? ? ? ? return orgnicPrice * 0.8;
? ? }
}我們看客戶類,我們需要客戶類幫我們完成玩家升級的功能怎棱。
public class Player {
? ? private Double totalAmount = 0D;//客戶在鵝廠消費的總額? ? private Double amount = 0D;//客戶單次消費金額? ? private CalPrice calPrice = new Orgnic();//每個客戶都有一個計算價格的策略方淤,初始都是普通計算,即原價? ? //客戶購買皮膚蹄殃,就會增加它的總額
? ? public void buy(Double amount) {
? ? ? ? this.amount = amount;
? ? ? ? totalAmount += amount;
? ? ? ? if (totalAmount > 30000) {//30000則改為金牌會員計算方式? ? ? ? ? ? calPrice = new GoldVip();
? ? ? ? } else if (totalAmount > 20000) {//類似? ? ? ? ? ? calPrice = new SuperVip();
? ? ? ? } else if (totalAmount > 10000) {//類似? ? ? ? ? ? calPrice = new Vip();
? ? ? ? }
? ? }
? ? //計算客戶最終要付的錢? ? public Double calLastAmount() {
? ? ? ? return calPrice.calPrice(amount);
? ? }
}
接下來是客戶端調(diào)用,系統(tǒng)會幫我們自動調(diào)整收費策略你踩。
public class Client {? ? public static void main(String[] args) {? ? ? ? Player player = new Player();player.buy(5000D);System.out.println("玩家需要付錢:"+ player.calLastAmount());player.buy(12000D);System.out.println("玩家需要付錢:"+ player.calLastAmount());player.buy(12000D);System.out.println("玩家需要付錢:"+ player.calLastAmount());player.buy(12000D);System.out.println("玩家需要付錢:"+ player.calLastAmount());}}
運行以后會發(fā)現(xiàn)诅岩,第一次是原價讳苦,第二次是九折,第三次是八折吩谦,最后一次則是七價鸳谜。這樣設計的好處是,客戶不再依賴于具體的收費策略式廷,依賴于抽象永遠是正確的咐扭。
在上面的基礎上,我們可以使用簡單工廠來稍微進行優(yōu)化
publicclassCalPriceFactory {privateCalPriceFactory(){}//根據(jù)客戶的總金額產(chǎn)生相應的策略publicstaticCalPricecreateCalPrice(Player customer){if(customer.getTotalAmount() >30000) {//3000則改為金牌會員計算方式returnnewGoldVip();? ? ? ? }elseif(customer.getTotalAmount() >20000) {//類似returnnewSuperVip();? ? ? ? }elseif(customer.getTotalAmount() >10000) {//類似returnnewVip();? ? ? ? }else{returnnewOrgnic();? ? ? ? }? ? }}
這樣就將制定策略的功能從客戶類分離了出來滑废,我們的客戶類可以變成這樣蝗肪。
publicclassPlayer {privateDouble totalAmount =0D;//客戶在鵝廠消費的總額privateDouble amount =0D;//客戶單次消費金額privateCalPrice calPrice =newOrgnic();//每個客戶都有一個計算價格的策略,初始都是普通計算蠕趁,即原價//客戶購買皮膚薛闪,就會增加它的總額publicvoidbuy(Double amount) {this.amount = amount;? ? ? ? totalAmount += amount;/* 變化點,我們將策略的制定轉移給了策略工廠俺陋,將這部分責任分離出去 */calPrice = CalPriceFactory.createCalPrice(this);? ? }//計算客戶最終要付的錢publicDoublecalLastAmount() {returncalPrice.calPrice(amount);? ? }publicDoublegetTotalAmount() {returntotalAmount;? ? }}
雖然結合簡單工廠模式豁延,我們的策略模式靈活了一些,但不免發(fā)現(xiàn)在工廠中多了if-else判斷腊状,也就是如果增加一個會員類別诱咏,我又得增加一個else-if語句,這是簡單工廠的缺點缴挖,對修改開放袋狞。
那有什么方法,可以較好的解決這個問題呢醇疼?那就是使用注解硕并, 所以我們需要給注解加入屬性上限和下限,用來表示策略生效的區(qū)間秧荆,用來解決總金額判斷的問題倔毙。
1.首先我們做一個注解,這個注解是用來給策略添加的乙濒,當中可以設置它的上下限
//這是有效價格區(qū)間注解陕赃,可以給策略添加有效區(qū)間的設置@Target(ElementType.TYPE)//表示只能給類添加該注解@Retention(RetentionPolicy.RUNTIME)//這個必須要將注解保留在運行時public@interfacePriceRegion{intmax()defaultInteger.MAX_VALUE;intmin()defaultInteger.MIN_VALUE;}
可以看到,我們只是使用這個注解來聲明每一個策略的生效區(qū)間颁股,于是對策略進行修改
@PriceRegion(max =10000)publicclassOrgnicimplementsCalPrice{@OverridepublicDoublecalPrice(Double orgnicPrice) {returnorgnicPrice;? ? }}
@PriceRegion(max=20000)publicclassVipimplementsCalPrice{@OverridepublicDoublecalPrice(Double orgnicPrice) {returnorgnicPrice *0.9;? ? }}
@PriceRegion(min=20000,max=30000)publicclassSuperVipimplementsCalPrice{@OverridepublicDoublecalPrice(Double orgnicPrice) {returnorgnicPrice *0.8;? ? }}
@PriceRegion(min=3000)publicclassGoldVipimplementsCalPrice{@OverridepublicDoublecalPrice(Double orgnicPrice) {returnorgnicPrice *0.7;? ? }}
接下來就是在策略工廠中去處理注解
publicclassCalPriceFactory{privatestaticfinalString CAL_PRICE_PACKAGE ="com.example.stragedemo";//這里是一個常量么库,表示我們掃描策略的包privateClassLoader classLoader = getClass().getClassLoader();privateList>calPriceList;//策略列表? ? //根據(jù)玩家的總金額產(chǎn)生相應的策略publicCalPricecreateCalPrice(Playerplayer) {//在策略列表查找策略for(Classclazz:calPriceList) {PriceRegion validRegion = handleAnnotation(clazz);//獲取該策略的注解//判斷金額是否在注解的區(qū)間if(player.getTotalAmount() > validRegion.min() && player.getTotalAmount() < validRegion.max()) {try{//是的話我們返回一個當前策略的實例returnclazz.newInstance();? ? ? ? ? ? ? ? }catch(Exceptione) {thrownewRuntimeException("策略獲得失敗");? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }thrownewRuntimeException("策略獲得失敗");? ? }//處理注解,我們傳入一個策略類甘有,返回它的注解privatePriceRegion handleAnnotation(Classclazz) {Annotation[] annotations = clazz.getDeclaredAnnotations();if(annotations ==null|| annotations.length ==0) {returnnull;? ? ? ? }for(int i =0; i < annotations.length; i++) {if(annotations[i]instanceofPriceRegion) {return(PriceRegion) annotations[i];? ? ? ? ? ? }? ? ? ? }returnnull;? ? }//單例privateCalPriceFactory() {? ? ? ? init();? ? }//在工廠初始化時要初始化策略列表privatevoid init() {? ? ? ? calPriceList =newArrayList>();File[]resources=getResources();//獲取到包下所有的class文件ClasscalPriceClazz=null;try{calPriceClazz = (Class)classLoader.loadClass(CalPrice.class.getName());//使用相同的加載器加載策略接口? ? ? ? }catch(ClassNotFoundExceptione1) {thrownewRuntimeException("未找到策略接口");? ? ? ? }for(int i =0; i < resources.length; i++) {try{//載入包下的類Classclazz=classLoader.loadClass(CAL_PRICE_PACKAGE+ "." +resources[i].getName().replace(".class", ""));? ? ? ? ? ? ? ? //判斷是否是CalPrice的實現(xiàn)類并且不是CalPrice它本身诉儒,滿足的話加入到策略列表if(CalPrice.class.isAssignableFrom(clazz) &&clazz!=calPriceClazz) {calPriceList.add((Class)clazz);? ? ? ? ? ? ? ? }? ? ? ? ? ? }catch(ClassNotFoundExceptione) {e.printStackTrace();? ? ? ? ? ? }? ? ? ? }? ? }//獲取掃描的包下面所有的class文件privateFile[] getResources() {try{? ? ? ? ? ? File file =newFile(classLoader.getResource(CAL_PRICE_PACKAGE.replace(".","/")).toURI());returnfile.listFiles(newFileFilter() {publicboolean accept(File pathname) {if(pathname.getName().endsWith(".class")) {//我們只掃描class文件returntrue;? ? ? ? ? ? ? ? ? ? }returnfalse;? ? ? ? ? ? ? ? }? ? ? ? ? ? });? ? ? ? }catch(URISyntaxException e) {thrownewRuntimeException("未找到策略資源");? ? ? ? }? ? }publicstaticCalPriceFactory getInstance() {returnCalPriceFactoryInstance.instance;? ? }privatestaticclassCalPriceFactoryInstance{privatestaticCalPriceFactory instance =newCalPriceFactory();? ? }}
雖然工廠里的邏輯增加了,但是解耦的效果達到了亏掀,現(xiàn)在我們隨便加入一個策略忱反,并設置好它的生效區(qū)間泛释,策略工廠就可以幫我們自動找到適應的策略。