策略模式學(xué)習(xí)筆記

1.概念

參考《JAVA與模式》的描述:

策略模式屬于對(duì)象的行為模式。其用意是針對(duì)一組算法,將每一個(gè)算法封裝到具有共同接口的獨(dú)立的類中,從而使得它們可以相互替換。

策略模式使得算法可以在不影響到客戶端的情況下發(fā)生變化。

2.層次結(jié)構(gòu)

  • 目的:策略模式是對(duì)算法的包裝,是把使用算法的責(zé)任和算法本身分割開來,委派給不同的對(duì)象管理。

  • 實(shí)現(xiàn):策略模式通常把一個(gè)系列的算法包裝到一系列的策略類里面,通常是作為一個(gè)接口的子類。即:“準(zhǔn)備一組算法,并將每一個(gè)算法封裝起來,使得它們可以互換”。

  • UML圖如下:

    image.png

     這個(gè)模式涉及到三個(gè)角色:
     (1)環(huán)境(Context)角色:持有一個(gè)Strategy的引用。
     (2)抽象策略(Strategy)角色:這是一個(gè)抽象角色,通常由一個(gè)接口或抽象類實(shí)
    現(xiàn)。此角色給出所有的具體策略類所需的接口。
    (3)具體策略(ConcreteStrategy)角色:包裝了相關(guān)的算法或行為。

3.實(shí)際案例

筆者在公司重構(gòu)金額計(jì)算模塊時(shí),需要根據(jù)產(chǎn)品類型 + 收費(fèi)代碼,計(jì)算應(yīng)收金額

業(yè)務(wù)邏輯如下:

  • 根據(jù)產(chǎn)品類型確定:費(fèi)率rate + 收費(fèi)規(guī)則rule
  • 每個(gè)產(chǎn)品有001和002兩種收費(fèi)項(xiàng)。根據(jù)產(chǎn)品費(fèi)率rate,去計(jì)算這兩種收費(fèi)項(xiàng)的應(yīng)收金額

原始代碼是這樣:

    // 產(chǎn)品類型
    int type = 1;
    // 往返都可以直達(dá)
    if (type == 1) {
        // 根據(jù)費(fèi)率rate1 + 收費(fèi)規(guī)則rule1, 計(jì)算收費(fèi)代碼為:001,002的款項(xiàng)應(yīng)收金額
        return;
    }

    // 產(chǎn)品類型為2
    if (type == 2) {
        // 根據(jù)費(fèi)率rate2 + 收費(fèi)規(guī)則rule2, 計(jì)算收費(fèi)代碼為:001,002的款項(xiàng)應(yīng)收金額
        return;
    }
    // 產(chǎn)品類型為3
    if (type == 3) {
        // 根據(jù)費(fèi)率rate3 + 收費(fèi)規(guī)則rule3, 計(jì)算收費(fèi)代碼為:001,002的款項(xiàng)應(yīng)收金額
        return;
    }
    // 產(chǎn)品類型為4
    else{
        // 根據(jù)費(fèi)率rate3 + 收費(fèi)規(guī)則rule3, 計(jì)算收費(fèi)代碼為:001,002的款項(xiàng)應(yīng)收金額
        return;
    }

4.案例優(yōu)化

如果我們遇到類似于上面的需求,第一反應(yīng)肯定是用if-else語句或者switch語句,根據(jù)不同的情況執(zhí)行不同的代碼。但是隨著需求越來越復(fù)雜,這么做的缺陷就慢慢的顯現(xiàn)了出來:除了產(chǎn)品類型之外,如果增加了其他判斷要素,如:收費(fèi)周期、標(biāo)的分類等,那么就會(huì)新增分支邏輯進(jìn)行判斷。

隨著需求的不斷增加,代碼的分支會(huì)越來越多,代碼最終會(huì)越來越難以維護(hù),所以策略模式出現(xiàn)了。

4.1原始的策略模式

  • 先定義一個(gè)model:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Receivables {
    private String desc;
}
  • 定義計(jì)算策略的接口:
public interface CalculateService {
    List<Receivables> CalculateReceivables();
}
  • 定義不同的策略實(shí)現(xiàn)類,有CalculateServiceImpl1,CalculateServiceImpl2,CalculateServiceImpl3,CalculateServiceImpl4(這里只展示一個(gè)):
public class CalculateServiceImpl1 implements CalculateService {

    @Override
    public List<Receivables> CalculateReceivables() {
        List<Receivables> list = new ArrayList<>();
        list.add(new Receivables("產(chǎn)品1應(yīng)收金額: 收費(fèi)項(xiàng)001--金額1000.00"));
        list.add(new Receivables("產(chǎn)品1應(yīng)收金額: 收費(fèi)項(xiàng)002--金額2000.00"));
        return list;
    }
}
@Service
public class CalculateServiceImpl2 implements CalculateService {

    @Override
    public List<Receivables> CalculateReceivables() {
        List<Receivables> list = new ArrayList<>();
        list.add(new Receivables("產(chǎn)品2應(yīng)收金額: 收費(fèi)項(xiàng)001--金額2100.00"));
        list.add(new Receivables("產(chǎn)品2應(yīng)收金額: 收費(fèi)項(xiàng)002--金額6666.00"));
        return list;
    }
}
  • 在上下文中定義:產(chǎn)品類型 與 計(jì)算策略 的映射關(guān)系 --- 通過map去記錄
    并提供 calculate( ) 接口,根據(jù)不同的產(chǎn)品類型,去調(diào)用不同的應(yīng)收金額計(jì)算策略
public class CalculateStrategy {
    private static Map<Integer, CalculateService> strategyMap = new HashMap<>();

    static {
        strategyMap.put(1, new CalculateServiceImpl1());
        strategyMap.put(2, new CalculateServiceImpl2());
        strategyMap.put(3, new CalculateServiceImpl3());
        strategyMap.put(4, new CalculateServiceImpl4());
    }

    public List<String> calculate(Integer type) {
        List<Receivables> receivablesList = strategyMap.get(type).CalculateReceivables();
        List<String> collect = receivablesList.stream()
                .map(Receivables::getDesc)
                .collect(Collectors.toList());
        return collect;
    }
}
  • controller層調(diào)用:
@RestController
@RequestMapping("/calculate")
@Slf4j
public class CalculateController {
    private static final Logger logger = LoggerFactory.getLogger(CalculateController.class);

    @Autowired
    private CalculateStrategy calculateStrategy;

    @GetMapping("/strategy-test")
    public void test(@RequestParam("type") String type) {
        List<String> calculate = calculateStrategy.calculate(Integer.parseInt(type));
        calculate.forEach(logger::info);
    }
}
  • 運(yùn)行結(jié)果:
2021-02-16 22:08:03.731  INFO 19176 --- [nio-8080-exec-1] c.q.s.controller.CalculateController     : 產(chǎn)品2應(yīng)收金額: 收費(fèi)項(xiàng)001--金額2100.00
2021-02-16 22:08:03.732  INFO 19176 --- [nio-8080-exec-1] c.q.s.controller.CalculateController     : 產(chǎn)品2應(yīng)收金額: 收費(fèi)項(xiàng)002--金額6666.00

4.2與spring結(jié)合的策略模式

4.2.1方式1

通過@Autowired + @PostConstruct 注入依賴,并將映射關(guān)系加到strategyMap中

@Service
public class CalculateStrategy {
    @Autowired
    private CalculateServiceImpl1 calculateServiceImpl1;

    @Autowired
    private CalculateServiceImpl2 calculateServiceImpl2;

    @Autowired
    private CalculateServiceImpl3 calculateServiceImpl3;

    @Autowired
    private CalculateServiceImpl4 calculateServiceImpl4;

    private static Map<Integer, CalculateService> strategyMap = new HashMap<>();

    @PostConstruct
    public void init() {
        strategyMap.put(1, calculateServiceImpl1);
        strategyMap.put(2, calculateServiceImpl2);
        strategyMap.put(3, calculateServiceImpl3);
        strategyMap.put(4, calculateServiceImpl4);
    }

//    static {
//        strategyMap.put(1, calculateServiceImpl1);
//        strategyMap.put(2, calculateServiceImpl2);
//        strategyMap.put(3, calculateServiceImpl3);
//        strategyMap.put(4, calculateServiceImpl4);
//    }

    public List<String> calculate(Integer type) {
        List<Receivables> receivablesList = strategyMap.get(type).CalculateReceivables();
        List<String> collect = receivablesList.stream()
                .map(Receivables::getDesc)
                .collect(Collectors.toList());
        return collect;
    }
}

運(yùn)行結(jié)果同4.1

4.2.2方式2

  • Spring有個(gè)技巧:自動(dòng)注入Map類型

具體說明:直接通過Map<String, CalculateService> 注入依賴,這里:beanName是map中的key,bean對(duì)應(yīng)的接口(實(shí)現(xiàn)類)是map中的value

  • 首先,@Service注解標(biāo)明beanName
@Service("2")
public class CalculateServiceImpl2 implements CalculateService {

    @Override
    public List<Receivables> CalculateReceivables() {
        List<Receivables> list = new ArrayList<>();
        list.add(new Receivables("產(chǎn)品2應(yīng)收金額: 收費(fèi)項(xiàng)001--金額2100.00"));
        list.add(new Receivables("產(chǎn)品2應(yīng)收金額: 收費(fèi)項(xiàng)002--金額6666.00"));
        return list;
    }
}

-其次,上下文中自動(dòng)注入Map<String, CalculateService>

@Service
public class CalculateStrategy {
    @Autowired
    private Map<String, CalculateService> strategyMap;

    public List<String> calculate(Integer type) {
        List<Receivables> receivablesList = strategyMap.get(String.valueOf(type)).CalculateReceivables();
        List<String> collect = receivablesList.stream()
                .map(Receivables::getDesc)
                .collect(Collectors.toList());
        return collect;
    }
}

結(jié)果同4.1

5.小結(jié)

5.1優(yōu)點(diǎn)

  • 策略的調(diào)用和具體策略的實(shí)現(xiàn)是松耦合關(guān)系。上下文只需要知道要使用哪一個(gè)實(shí)現(xiàn)Strategy接口的實(shí)例,但不需要知道具體是哪一個(gè)類。

    注:如果這些算法具有公共的功能,可以將接口變?yōu)槌橄箢悾瑢⒐补δ芊诺匠橄蟾割惱锩妗?/strong>

  • 策略模式滿足“開閉原則”,當(dāng)增加新的具體策略時(shí),不需要修改上下文類的代碼,就可以引用新的策略的實(shí)例。

5.2缺點(diǎn)

  • 增加了對(duì)象的數(shù)量
    由于策略模式將每個(gè)具體的算法都單獨(dú)封裝為一個(gè)策略類,如果可選的策略有很多的話,那對(duì)象的數(shù)量也會(huì)很多。
  • 只適合偏平的算法結(jié)構(gòu)
    由于策略模式的各個(gè)策略實(shí)現(xiàn)是平等的關(guān)系(可相互替換),實(shí)際上就構(gòu)成了一個(gè)扁平的算法結(jié)構(gòu)。即一個(gè)策略接口下面有多個(gè)平等的策略實(shí)現(xiàn),但是運(yùn)行時(shí)只能有一個(gè)算法被使用。這就限制了算法的使用層級(jí),且不能被嵌套。

5.3使用場(chǎng)景

  • 一個(gè)類定義了多種行為,并且這些行為在這個(gè)類的方法中以多個(gè) if-else 條件語句的形式出現(xiàn),那么使用策略模式避免在類中使用大量的條件語句。
  • 不希望暴露復(fù)雜的,與算法相關(guān)的數(shù)據(jù)結(jié)構(gòu),那么可以使用策略模式封裝算法,即:將算法分別封裝到具體策略實(shí)現(xiàn)類中。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容