詳解Java如何優(yōu)雅的使用策略模式
最近這段時(shí)間,想給大家分享一下設(shè)計(jì)模式的一些用法以及在項(xiàng)目中怎么運(yùn)用。
設(shè)計(jì)模式是軟件設(shè)計(jì)中常見(jiàn)問(wèn)題的典型解決方案。 它們就像能根據(jù)需求進(jìn)行調(diào)整的預(yù)制藍(lán)圖, 可用于解決代碼中反復(fù)出現(xiàn)的設(shè)計(jì)問(wèn)題。
今天就拿其中一個(gè)問(wèn)題來(lái)分析,使用策略模式來(lái)解決問(wèn)題,沒(méi)有了解過(guò)策略模式或者長(zhǎng)時(shí)間不用已經(jīng)忘了策略模式的小伙伴先來(lái)簡(jiǎn)單了解一下策略模式吧。
什么是策略模式
策略模式是一種行為型模式,它將對(duì)象和行為分開(kāi),將行為定義為 一個(gè)行為接口 和 具體行為的實(shí)現(xiàn)。策略模式最大的特點(diǎn)是行為的變化,行為之間可以相互替換。每個(gè)if判斷都可以理解為就是一個(gè)策略。本模式使得算法可獨(dú)立于使用它的用戶而變化。
簡(jiǎn)單理解就是,針對(duì)不同的場(chǎng)景,使用不同的策略進(jìn)行處理。
策略模式結(jié)構(gòu)
- Strategy 接口定義了一個(gè)算法族,它們都實(shí)現(xiàn)了 behavior() 方法。
- Context 是使用到該算法族的類,其中的 doSomething() 方法會(huì)調(diào)用 behavior(),setStrategy(Strategy) 方法可以動(dòng)態(tài)地改變 strategy 對(duì)象,也就是說(shuō)能動(dòng)態(tài)地改變 Context 所使用的算法。
策略模式適用場(chǎng)景
- 如果在一個(gè)系統(tǒng)里面有許多類,它們之間的區(qū)別僅在于它們 的行為,那么使用策略模式可以動(dòng)態(tài)地讓一個(gè)對(duì)象在許多行 為中選擇一種行為。
- 一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種。
- 如果一個(gè)對(duì)象有很多的行為,如果不用恰當(dāng)?shù)哪J?,這些行 為就只好使用多重的條件選擇語(yǔ)句來(lái)實(shí)現(xiàn)。
- 不希望客戶端知道復(fù)雜的、與算法相關(guān)的數(shù)據(jù)結(jié)構(gòu),在具體策略類中封裝算法和相關(guān)的數(shù)據(jù)結(jié)構(gòu),提高算法的保密性與安全性。
生活中比較常見(jiàn)的應(yīng)用模式有:
- 電商網(wǎng)站支付方式,一般分為銀聯(lián)、微信、支付寶,可以采用策略模式。
- 電商網(wǎng)站活動(dòng)方式,一般分為滿減送、限時(shí)折扣、包郵活動(dòng),拼團(tuán)等可以采用策略模式。
簡(jiǎn)單示例
場(chǎng)景:最近太熱了,想要降降溫,有什么辦法呢
首先,定義一個(gè)降溫策略的接口
public interface CoolingStrategy { /** * 處理方式 */ void handle(); }
定義3種降溫策略;實(shí)現(xiàn)策略接口
public class IceCoolingStrategy implements CoolingStrategy { @Override public void handle() { System.out.println("使用冰塊降溫"); } }
public class FanCoolingStrategy implements CoolingStrategy { @Override public void handle() { System.out.println("使用風(fēng)扇降溫"); } }
public class AirConditionerCoolingStrategy implements CoolingStrategy { @Override public void handle() { System.out.println("使用空調(diào)降溫"); } }
定義一個(gè)降溫策略的上下文
public class CoolingStrategyContext { private final CoolingStrategy strategy; public CoolingStrategyContext(CoolingStrategy strategy) { this.strategy = strategy; } public void coolingHandle() { strategy.handle(); } }
測(cè)試
public class Main { public static void main(String[] args) { CoolingStrategyContext context = new CoolingStrategyContext(new FanCoolingStrategy()); context.coolingHandle(); context = new CoolingStrategyContext(new AirConditionerCoolingStrategy()); context.coolingHandle(); context = new CoolingStrategyContext(new IceCoolingStrategy()); context.coolingHandle(); } }
運(yùn)行結(jié)果:
使用風(fēng)扇降溫
使用空調(diào)降溫
使用冰塊降溫
以上就是一個(gè)策略模式的簡(jiǎn)單實(shí)現(xiàn)
項(xiàng)目實(shí)戰(zhàn)
場(chǎng)景
模擬在購(gòu)買(mǎi)商品時(shí)候使用的各種類型優(yōu)惠券(滿減、直減、折扣、n元購(gòu))
這個(gè)場(chǎng)景幾乎也是大家的一個(gè)日常購(gòu)物省錢(qián)渠道,購(gòu)買(mǎi)商品的時(shí)候都希望找一些優(yōu)惠券,讓購(gòu)買(mǎi)的商品更加實(shí)惠。而且到了大促的時(shí)候就會(huì)有更多的優(yōu)惠券需要計(jì)算那些商品一起購(gòu)買(mǎi)更加優(yōu)惠!
用一坨坨代碼實(shí)現(xiàn)
/** * 優(yōu)惠券折扣計(jì)算接口 * <p> * 優(yōu)惠券類型; * 1. 直減券 * 2. 滿減券 * 3. 折扣券 * 4. n元購(gòu) */ public class CouponDiscountService { public double discountAmount(int type, double typeContent, double skuPrice, double typeExt) { // 1. 直減券 if (1 == type) { return skuPrice - typeContent; } // 2. 滿減券 if (2 == type) { if (skuPrice < typeExt) return skuPrice; return skuPrice - typeContent; } // 3. 折扣券 if (3 == type) { return skuPrice * typeContent; } // 4. n元購(gòu) if (4 == type) { return typeContent; } return 0D; } }
- 以上是不同類型的優(yōu)惠券計(jì)算折扣后的實(shí)際金額。
- 入?yún)?;?yōu)惠券類型、優(yōu)惠券金額、商品金額,因?yàn)橛行﹥?yōu)惠券是滿多少減少多少,所以增加了
typeExt
類型。這也是方法的不好擴(kuò)展性問(wèn)題。 - 最后是整個(gè)的方法體中對(duì)優(yōu)惠券抵扣金額的實(shí)現(xiàn),最開(kāi)始可能是一個(gè)最簡(jiǎn)單的優(yōu)惠券,后面隨著產(chǎn)品功能的增加,不斷的擴(kuò)展
if
語(yǔ)句。實(shí)際的代碼可能要比這個(gè)多很多
策略模式重構(gòu)代碼
- 整體的結(jié)構(gòu)模式并不復(fù)雜,主要體現(xiàn)的不同類型的優(yōu)惠券在計(jì)算優(yōu)惠券方式的不同計(jì)算策略。
- 這里包括一個(gè)接口類(
ICouponDiscount
)以及四種優(yōu)惠券類型的實(shí)現(xiàn)方式。 - 最后提供了策略模式的上下控制類處理,整體的策略服務(wù)。
代碼實(shí)現(xiàn)
優(yōu)惠券接口
public interface ICouponDiscount<T> { /** * 優(yōu)惠券金額計(jì)算 * @param couponInfo 券折扣信息;直減、滿減、折扣、N元購(gòu) * @param skuPrice sku金額 * @return 優(yōu)惠后金額 */ BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice); }
- 定義了優(yōu)惠券折扣接口,也增加了泛型用于不同類型的接口可以傳遞不同的類型參數(shù)。
- 接口中包括商品金額以及出參返回最終折扣后的金額,這里在實(shí)際開(kāi)發(fā)中會(huì)比現(xiàn)在的接口參數(shù)多一些,但核心邏輯是這些。
優(yōu)惠券接口實(shí)現(xiàn)
滿減
public class MJCouponDiscount implements ICouponDiscount<Map<String,String>> { /** * 滿減計(jì)算 * 1. 判斷滿足x元后-n元,否則不減 * 2. 最低支付金額1元 */ public BigDecimal discountAmount(Map<String,String> couponInfo, BigDecimal skuPrice) { String x = couponInfo.get("x"); String o = couponInfo.get("n"); // 小于商品金額條件的,直接返回商品原價(jià) if (skuPrice.compareTo(new BigDecimal(x)) < 0) return skuPrice; // 減去優(yōu)惠金額判斷 BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(o)); if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE; return discountAmount; } }
直減
public class ZJCouponDiscount implements ICouponDiscount<Double> { /** * 直減計(jì)算 * 1. 使用商品價(jià)格減去優(yōu)惠價(jià)格 * 2. 最低支付金額1元 */ public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) { BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(couponInfo)); if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE; return discountAmount; } }
折扣
public class ZKCouponDiscount implements ICouponDiscount<Double> { /** * 折扣計(jì)算 * 1. 使用商品價(jià)格乘以折扣比例,為最后支付金額 * 2. 保留兩位小數(shù) * 3. 最低支付金額1元 */ public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) { BigDecimal discountAmount = skuPrice.multiply(new BigDecimal(couponInfo)).setScale(2, BigDecimal.ROUND_HALF_UP); if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE; return discountAmount; } }
N元購(gòu)
public class NYGCouponDiscount implements ICouponDiscount<Double> { /** * n元購(gòu)購(gòu)買(mǎi) * 1. 無(wú)論原價(jià)多少錢(qián)都固定金額購(gòu)買(mǎi) */ public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) { return new BigDecimal(couponInfo); } }
以上是四種不同類型的優(yōu)惠券計(jì)算折扣金額的策略方式,可以從代碼中看到每一種優(yōu)惠方式的優(yōu)惠金額。
策略控制類
public class Context<T> { private ICouponDiscount<T> couponDiscount; public Context(ICouponDiscount<T> couponDiscount) { this.couponDiscount = couponDiscount; } public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) { return couponDiscount.discountAmount(couponInfo, skuPrice); } }
- 策略模式的控制類主要是外部可以傳遞不同的策略實(shí)現(xiàn),在通過(guò)統(tǒng)一的方法執(zhí)行優(yōu)惠策略計(jì)算。
- 另外這里也可以包裝成map結(jié)構(gòu),讓外部只需要對(duì)應(yīng)的泛型類型即可使用相應(yīng)的服務(wù)。
測(cè)試類
public class ApiTest { private Logger logger = LoggerFactory.getLogger(ApiTest.class); @Test public void test_zj() { // 直減;100-10,商品100元 Context<Double> context = new Context<Double>(new ZJCouponDiscount()); BigDecimal discountAmount = context.discountAmount(10D, new BigDecimal(100)); logger.info("測(cè)試結(jié)果:直減優(yōu)惠后金額 {}", discountAmount); } @Test public void test_mj() { // 滿100減10,商品100元 Context<Map<String,String>> context = new Context<Map<String,String>>(new MJCouponDiscount()); Map<String,String> mapReq = new HashMap<String, String>(); mapReq.put("x","100"); mapReq.put("n","10"); BigDecimal discountAmount = context.discountAmount(mapReq, new BigDecimal(100)); logger.info("測(cè)試結(jié)果:滿減優(yōu)惠后金額 {}", discountAmount); } @Test public void test_zk() { // 折扣9折,商品100元 Context<Double> context = new Context<Double>(new ZKCouponDiscount()); BigDecimal discountAmount = context.discountAmount(0.9D, new BigDecimal(100)); logger.info("測(cè)試結(jié)果:折扣9折后金額 {}", discountAmount); } @Test public void test_nyg() { // n元購(gòu);100-10,商品100元 Context<Double> context = new Context<Double>(new NYGCouponDiscount()); BigDecimal discountAmount = context.discountAmount(90D, new BigDecimal(100)); logger.info("測(cè)試結(jié)果:n元購(gòu)優(yōu)惠后金額 {}", discountAmount); } }
- 以上四組測(cè)試分別驗(yàn)證了不同類型優(yōu)惠券的優(yōu)惠策略,測(cè)試結(jié)果是滿足我們的預(yù)期。
- 這里四種優(yōu)惠券最終都是在原價(jià)100元上折扣10元,最終支付90元。
總結(jié)
通過(guò)策略設(shè)計(jì)模式的使用可以把我們方法中的 if 語(yǔ)句優(yōu)化掉,大量的 if 語(yǔ)句使用會(huì)讓代碼難以擴(kuò)展,也不好維護(hù),同時(shí)在后期遇到各種問(wèn)題也很難維護(hù)。在使用這樣的設(shè)計(jì)模式后可以很好的滿足隔離性與和擴(kuò)展性,對(duì)于不斷新增的需求也非常方便承接。
了解策略模式的優(yōu)點(diǎn)和缺點(diǎn),合理的使用策略模式,會(huì)讓你的代碼更加的整潔優(yōu)雅。
以上就是詳解Java如何優(yōu)雅的使用策略模式的詳細(xì)內(nèi)容,更多關(guān)于Java使用策略模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
用Java連接sqlserver數(shù)據(jù)庫(kù)時(shí)候幾個(gè)jar包的區(qū)別分析
這篇文章主要介紹了用Java連接sqlserver數(shù)據(jù)庫(kù)時(shí)候幾個(gè)jar包的區(qū)別分析,需要的朋友可以參考下2014-10-10mybatis動(dòng)態(tài)拼接實(shí)現(xiàn)有條件的插入
這篇文章主要介紹了mybatis動(dòng)態(tài)拼接實(shí)現(xiàn)有條件的插入,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02SpringCloud Ribbon與OpenFeign詳解如何實(shí)現(xiàn)服務(wù)調(diào)用
這篇文章主要介紹了SpringCloud Ribbon與OpenFeign實(shí)現(xiàn)服務(wù)調(diào)用的過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09Spring Boot中實(shí)現(xiàn)定時(shí)任務(wù)應(yīng)用實(shí)踐
定時(shí)任務(wù)一般是項(xiàng)目中都需要用到的,可以用于定時(shí)處理一些特殊的任務(wù)。下面這篇文章主要給大家介紹了關(guān)于Spring Boot中實(shí)現(xiàn)定時(shí)任務(wù)應(yīng)用實(shí)踐的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。2018-05-05springboot實(shí)現(xiàn)獲取當(dāng)前服務(wù)器IP及當(dāng)前項(xiàng)目使用的端口號(hào)Port
這篇文章主要介紹了springboot實(shí)現(xiàn)獲取當(dāng)前服務(wù)器IP及當(dāng)前項(xiàng)目使用的端口號(hào)Port方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12使用java技術(shù)抓取網(wǎng)站上彩票雙色球信息詳解
這篇文章主要介紹了使用java技術(shù)抓取網(wǎng)站上彩票雙色球信息詳解,web結(jié)果由html+js+css組成,html結(jié)構(gòu)都有一定的規(guī)范,數(shù)據(jù)動(dòng)態(tài)交互可以通過(guò)js實(shí)現(xiàn)。,需要的朋友可以參考下2019-06-06基于Java?利用Mybatis實(shí)現(xiàn)oracle批量插入及分頁(yè)查詢
這篇文章主要介紹了基于Java?利用Mybatis實(shí)現(xiàn)oracle批量插入及分頁(yè)查詢,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,需要的小伙伴可以參考一下2022-07-07