(優(yōu)化if-else)工廠+ 策略設(shè)計模式在 Springboot 項目中使用案例

工廠模式和策略模式:

在Java的設(shè)計模式中拥刻,工廠模式和策略模式都是很常見且實用的模式扁位。它們各自解決了不同的問題蒜危,在復雜的系統(tǒng)設(shè)計中墨技,這兩種模式往往會結(jié)合使用役拴,以提升代碼的靈活性糊探、可維護性和擴展性。
1河闰、工廠模式(Factory Pattern)是一種創(chuàng)建型設(shè)計模式科平,旨在定義一個接口來創(chuàng)建對象,但讓子類決定實例化哪一個類姜性。工廠模式讓一個類的實例化延遲到其子類瞪慧。
2、策略模式(Strategy Pattern)是一種行為型設(shè)計模式部念,定義了一系列算法弃酌,并將每個算法封裝起來,使它們可以互換使用儡炼。策略模式使得算法可以在不影響客戶端的情況下發(fā)生變化妓湘。
1. 需求背景
假設(shè)我們需要開發(fā)一個訂單處理系統(tǒng),系統(tǒng)需要根據(jù)不同的支付方式(如信用卡乌询、PayPal榜贴、比特幣等)進行不同的處理。不同的支付方式有不同的處理策略妹田,并且系統(tǒng)需要支持將來擴展新的支付方式唬党。
2. 設(shè)計思路
使用策略模式:將不同的支付方式封裝為不同的策略,實現(xiàn)支付的多樣化處理鬼佣。
使用工廠模式:工廠模式負責根據(jù)用戶選擇的支付方式創(chuàng)建和選擇相應(yīng)的支付策略對象驶拱。執(zhí)行業(yè)務(wù)邏輯。

業(yè)務(wù)場景:

1晶衷、營銷系統(tǒng)屯烦,根據(jù)不同業(yè)務(wù)渠道推送消息坷随,比如:微信消息推送、釘釘工作通知驻龟、短信温眉、郵件通知
2、系統(tǒng)支付翁狐,業(yè)務(wù)選擇不同支付方式类溢,執(zhí)行不同的業(yè)務(wù)邏輯和處理措施。比如:微信露懒、支付寶闯冷、銀聯(lián)等
3、系統(tǒng)登錄懈词,根據(jù)需求選擇不同登錄方式蛇耀,觸發(fā)后臺不同的業(yè)務(wù)執(zhí)行邏輯。比如:微信坎弯、釘釘纺涤、短信、支付寶等不同的登錄渠道進行授權(quán)抠忘,完成業(yè)務(wù)系統(tǒng)登錄

解決方案(如下圖):

1撩炊、在業(yè)務(wù)層使用 if-else 判斷類型,去執(zhí)行不同的業(yè)務(wù)邏輯崎脉。
2拧咳、使用工廠模式+策略設(shè)計模式,同時結(jié)合SpringBoot注解選擇不同的策略囚灼,實現(xiàn)不同的業(yè)務(wù)邏輯骆膝。

代碼示例

定義枚舉

@AllArgsConstructor
@Getter
public enum SaleOrderEnum {

    SHOP_SALE_ORDER("10001", "店鋪業(yè)務(wù)零售單"),
    SUPPLY_SALE_ORDER("10002","供應(yīng)鏈直銷單"),
    ;

    private static final Map<Long, SaleOrderEnum > ORDER_MAP = new HashMap<>(32);

    static {
        for (SaleOrderEnum orderStatusEnum : SaleOrderEnum .values()) {
            ORDER_STATUS_MAP.put(orderStatusEnum.getCode(), orderStatusEnum);
        }
    }

    public static SaleOrderEnum findByCode(String code) {
        // 這里可以直接調(diào)用getSaleOrdeEnum()方法也可以獲取。
        for (SaleOrderEnum orderEnum : SaleOrderEnum.values()) {
            if (orderEnum.getCode().equals(code)) {
                return orderEnum;
            }
        }
        throw new IllegalArgumentException("Please check the passed parameters");
    }

    private String code;
    private String name;

   public static SaleOrderEnum getSaleOrdeEnum(Long code) {
        return ORDER_STATUS_MAP.get(code);
    }
}

定義注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface OrderStrategyType {
    SaleOrderEnum orderType();
}

描述:定義這個注解的目的灶体,是在不同的策略上使用的阅签,在工廠中就可以根據(jù)注解查找到所有的策略,并保存進 map 中映射起來赃春,一旦有參數(shù)進來愉择,就去 map 中去查找對應(yīng)的策略,就可以執(zhí)行對應(yīng)的方法了织中,減少if-else代碼塊優(yōu)化代碼锥涕,同時可以保證代碼的擴展性。

定義策略類的接口

首先定義一個策略接口狭吼,然后編寫業(yè)務(wù)類去實現(xiàn)

public interface OrderStrategy {
    /**
     * 查詢數(shù)據(jù)方法
     *
     * @return 返回結(jié)果
     */
    List<Object> executeOrderBiz();
}
1层坠、門店業(yè)務(wù)實現(xiàn)類、實現(xiàn)工單策略處理接口刁笙,編寫業(yè)務(wù)執(zhí)行邏輯
@Slf4j
@Component
@RequiredArgsConstructor
@OrderStrategyType(orderType = SaleOrderEnum.SHOP_SALE_ORDER)
public class ShopSaleOrderStrategy implements OrderStrategy {
    @Override
    public List<Object> executeOrderBiz() {
        log.info("執(zhí)行門店銷售單業(yè)務(wù)功能......");
        return Arrays.asList("業(yè)務(wù)工單1", "業(yè)務(wù)工單2", "業(yè)務(wù)工單3");
    }
}
2破花、供應(yīng)鏈業(yè)務(wù)實現(xiàn)類谦趣、實現(xiàn)工單策略處理接口,編寫業(yè)務(wù)執(zhí)行邏輯
@Slf4j
@Component
@RequiredArgsConstructor
@OrderStrategyType(orderType = SaleOrderEnum.SUPPLY_SALE_ORDER)
public class SupplySaleOrderStrategy implements OrderStrategy {
    @Override
    public List<Object> executeOrderBiz() {
        log.info("執(zhí)行供應(yīng)鏈銷售單業(yè)務(wù)功能......");
        return Arrays.asList("供應(yīng)鏈工單1","供應(yīng)鏈工單2");
    }
}
注意:我們要把具體的業(yè)務(wù)實現(xiàn)類策略交給 Spring 管理座每,所以加上了 @Component 注解前鹅。

定義工廠類(業(yè)務(wù)調(diào)用通用入口)

@Component
@RequiredArgsConstructor
public class OrderStrategyFactory {
    /**
     * Spring會注入所有OrderStrategy的業(yè)務(wù)實現(xiàn)類
     */
    private final List<OrderStrategy> strategyList;

    private static Map<SaleOrderEnum, OrderStrategy> map = new ConcurrentHashMap<>();

    public static OrderStrategy getStrategy(String code){
        return map.get(SaleOrderEnum.findByCode(code));
    }

    public OrderStrategyFactory(List<OrderStrategy> strategyList) {
        this.strategyList = strategyList;
    }

    @PostConstruct
    public void init(){
        strategyList.forEach(i -> {
            OrderStrategyType annotation = AnnotationUtils.findAnnotation(i.getClass(), OrderStrategyType.class);
            map.put(annotation.orderType(),i);
        });
    }
}
上面案例代碼中我們已經(jīng)把具體的策略,像 SupplySaleOrderStrategy峭梳、ShopSaleOrderStrategy 這些策略的實現(xiàn)舰绘,交給 spring 管理了,也就是說我們可以從 spring 的容器中獲取到對應(yīng)的 bean葱椭,當然也可以進行依賴注入,這就是Spring中IOC容器特性孵运。
所以在工廠類中秦陋,我們定義了一個 strategyList 屬性,并且使用 @RequiredArgsConstructor 注解治笨,就是使用構(gòu)造器的方式將屬性注入驳概。所以當 spring 容器啟動的時候,就會查找所有策略大磺,并注入到工廠類的屬性當中抡句。這是 spring 的依賴注入的原理探膊。
我們使用 @PostContruct 注解杠愧,當工廠類加載完畢之后,就會執(zhí)行 init()方法逞壁。這時候流济,因為 orderStrategyFactory 這個 bean 已經(jīng)在 spring 容器中加載完畢了,并且也注入了屬性腌闯,所以這個 strategyList 就有值了绳瘟,我們可以遍歷這個列表,獲取到對應(yīng) OrderStrategyType 這個注解的信息姿骏,然后放入到 map 當中糖声,后面我們就可以根據(jù)枚舉類型將不同的策略對應(yīng)起來了。

使用層面:業(yè)務(wù)調(diào)用示例(使用案例)

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping(("/getList")
    public List<Object> getList(@RequestParam String type){
        OrderStrategy strategy = OrderStrategyFactory.getStrategy(type);
        return strategy.executeOrderBiz();
    }
}

總結(jié):

這樣就可以避免代碼中的if-else分瘦,同時可以動態(tài)增加業(yè)務(wù)實現(xiàn)類蘸泻。
比如增加其他業(yè)務(wù)實現(xiàn)類,直接實現(xiàn)OrderStrategy 接口即可嘲玫,同時修改每個執(zhí)行業(yè)務(wù)的邏輯悦施,不會相互影響。使用時去团,我們就可以在枚舉類中加入對應(yīng)的類型抡诞,在具體的策略實現(xiàn)類中加入注解穷蛹,并指定對應(yīng)的枚舉類型,這樣我們就可以完成我們的業(yè)務(wù)需求了昼汗。

補充描述:上面實現(xiàn)接口肴熏、也可以繼承抽象類

定義業(yè)務(wù)抽象類

public abstract class OrderStrategy {
    public abstract List<Object> executeOrderBiz();
}

使用案例:將實現(xiàn)類中原來的implements 變成extends 其他代碼完全一致

@Slf4j
@Component
@RequiredArgsConstructor
@OrderStrategyType(orderType = SaleOrderEnum.SHOP_SALE_ORDER)
public class ShopSaleOrderStrategy extends OrderStrategy {
    @Override
    public List<Object> executeOrderBiz() {
        log.info("執(zhí)行門店銷售單業(yè)務(wù)功能......");
        return Arrays.asList("業(yè)務(wù)工單1", "業(yè)務(wù)工單2", "業(yè)務(wù)工單3");
    }
}
【精彩語錄】:我知道你是普通人,我們大家都是普通人顷窒。但是你知道嗎扮超?這個世界上大部分的奇跡,只不過是普普通通的人們蹋肮,將心意化作了行動而已出刷。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坯辩,隨后出現(xiàn)的幾起案子馁龟,更是在濱河造成了極大的恐慌,老刑警劉巖漆魔,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坷檩,死亡現(xiàn)場離奇詭異,居然都是意外死亡改抡,警方通過查閱死者的電腦和手機矢炼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阿纤,“玉大人句灌,你說我怎么就攤上這事∏肥埃” “怎么了胰锌?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長藐窄。 經(jīng)常有香客問我资昧,道長,這世上最難降的妖魔是什么荆忍? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任格带,我火速辦了婚禮,結(jié)果婚禮上刹枉,老公的妹妹穿的比我還像新娘叽唱。我一直安慰自己,他們只是感情好嘶卧,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布尔觉。 她就那樣靜靜地躺著,像睡著了一般芥吟。 火紅的嫁衣襯著肌膚如雪侦铜。 梳的紋絲不亂的頭發(fā)上专甩,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音钉稍,去河邊找鬼涤躲。 笑死,一個胖子當著我的面吹牛贡未,可吹牛的內(nèi)容都是我干的种樱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼俊卤,長吁一口氣:“原來是場噩夢啊……” “哼嫩挤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起消恍,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤岂昭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后狠怨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體约啊,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年佣赖,在試婚紗的時候發(fā)現(xiàn)自己被綠了恰矩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡憎蛤,死狀恐怖外傅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹂午,我是刑警寧澤栏豺,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布彬碱,位于F島的核電站豆胸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏巷疼。R本人自食惡果不足惜晚胡,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嚼沿。 院中可真熱鬧估盘,春花似錦、人聲如沸骡尽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攀细。三九已至箫踩,卻和暖如春爱态,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背境钟。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工锦担, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慨削。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓洞渔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缚态。 傳聞我的和親對象是個殘疾皇子磁椒,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353