策略模式

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ū)間泛释,策略工廠就可以幫我們自動找到適應的策略。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末温算,一起剝皮案震驚了整個濱河市怜校,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌注竿,老刑警劉巖茄茁,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異巩割,居然都是意外死亡裙顽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門喂分,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锦庸,“玉大人,你說我怎么就攤上這事蒲祈「氏簦” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵梆掸,是天一觀的道長扬卷。 經(jīng)常有香客問我,道長酸钦,這世上最難降的妖魔是什么怪得? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮卑硫,結果婚禮上徒恋,老公的妹妹穿的比我還像新娘。我一直安慰自己欢伏,他們只是感情好入挣,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著硝拧,像睡著了一般径筏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上障陶,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天滋恬,我揣著相機與錄音,去河邊找鬼抱究。 笑死恢氯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播酿雪,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼遏暴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了指黎?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤州丹,失蹤者是張志新(化名)和其女友劉穎醋安,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墓毒,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡吓揪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了所计。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柠辞。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖主胧,靈堂內(nèi)的尸體忽然破棺而出叭首,到底是詐尸還是另有隱情,我是刑警寧澤踪栋,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布焙格,位于F島的核電站,受9級特大地震影響夷都,放射性物質(zhì)發(fā)生泄漏眷唉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一囤官、第九天 我趴在偏房一處隱蔽的房頂上張望冬阳。 院中可真熱鬧,春花似錦党饮、人聲如沸肝陪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽见坑。三九已至,卻和暖如春捏检,著一層夾襖步出監(jiān)牢的瞬間荞驴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工贯城, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留熊楼,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像鲫骗,于是被迫代替她去往敵國和親犬耻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容