【java設計模式】(7)---策略模式(案例解析)


一、概念

1、理解策略模式

策略模式是一種行為型模式,它將對象和行為分開,將行為定義為 一個行為接口具體行為的實現。策略模式最大的特點是行為的變化,行為之間可以相互替換。每個if判斷都可以理解為就是一個策略。

2、策略模式特點

策略模式把對象本身和行為區分開來,因此我們整個模式也分為三個部分。

1、抽象策略類(Strategy):策略的抽象,行為方式的抽象
2、具體策略類(ConcreteStrategy):具體的策略實現,每一種行為方式的具體實現。
3、環境類(Context):用來封裝具體行為,操作策略的上下文環境。

3、舉例理解(打車)

這里舉個簡單的例子,來理解開發中運用策略模式的場景。

有一個打車軟件,現在有三種計費模式給 用戶 選擇,1、拼車 2、快車 3、豪車這個時候用戶就是對象,三種計費方式就是行為,可以根據不同的行為計算不同不通的費用。

1)傳統實現方式

代碼

    /**
      * @Description: 這里只展示計費最終費用示例
      *
      * @param  type 計費類型
      * @param  originalPrice 原始價格
      */
    public Double calculationPrice(String type, Double originalPrice) {

        //拼車計費
        if (type.equals("pc")) {
            return originalPrice * 0.5;
        }
        //快車計費
        if (type.equals("kc")) {
            return originalPrice * 1;
        }
        //豪車計費
        if (type.equals("hc")) {
            return originalPrice * 2;
        }
        return originalPrice;
    }

傳統的實現方式,通過傳統if代碼判斷。這樣就會導致后期的維護性非常差。當后期需要新增計費方式,還需要在這里再加上if(),也不符合設計模式的開閉原則。

2)策略模式實現

抽象策略類

/**
 * 出行策略接口
 */
public interface PriceStrategy {
	/**
	 * @param originalPrice 原始價格
	 * @return  計算后的價格
	 */
	Double countPrice(Double originalPrice);
}

具體策略實現類

/**
  * @Description: 拼車的計費方式
  */
public class PcStrategy implements PriceStrategy {
    @Override
    public Double countPrice(Double originalPrice) {
        return originalPrice * 0.5;
    }
}

/**
  * @Description: 快車的計費方式
  */
public class KcStrategy implements PriceStrategy {
    @Override
    public Double countPrice(Double originalPrice) {
        return originalPrice * 1;
    }
}

/**
  * @Description: 拼車的計費方式
  */
public class HcStrategy implements PriceStrategy {
    @Override
    public Double countPrice(Double originalPrice) {
        return originalPrice * 2;
    }
}

環境類

也叫做上下文類或環境類,起承上啟下封裝作用。

/**
 * 負責和具體的策略類交互
 * 這樣的話,具體的算法和直接的客戶端調用分離了,使得算法可以獨立於客戶端獨立的變化。
 * 如果使用spring的依賴注入功能,還可以通過配置文件,動態的注入不同策略對象,動態的切換不同的算法.
 */
public class PriceContext {

    /**
     * 出行策略接口
     */
    private PriceStrategy riceStrategy;
    /**
     * 構造函數注入
     */
    public PriceContext(PriceStrategy riceStrategy) {
        this.riceStrategy = riceStrategy;
    }
    /**
     * 計算價格
     */
    public Double countPrice(Double originalPrice) {
        return riceStrategy.countPrice(originalPrice);
    }
}

測試類

    public static void main(String[] args) {
        //具體行為策略
        PriceStrategy pcStrategy = new PcStrategy();
        PriceStrategy kcStrategy = new KcStrategy();
        PriceStrategy hcStrategy = new HcStrategy();

        //用戶選擇不同的策略
        PriceContext pcContext = new PriceContext(pcStrategy);
        PriceContext kcContext = new PriceContext(kcStrategy);
        PriceContext hcContext = new PriceContext(hcStrategy);

        System.out.println("拼車價格 = " +  pcContext.countPrice(10D));
        System.out.println("快車價格 = " +  kcContext.countPrice(10D));
        System.out.println("豪車價格 = " +  hcContext.countPrice(10D));
    }

運行結果

拼車價格 = 5.0
快車價格 = 10.0
豪車價格 = 20.0

整理流程就是這個樣的,這里有一點需要注意 我在做測試的時候具體策略對象都是new出來,而實際運用應該通過spring來管理,這樣才能做到真正的開閉原則。下面會在舉例。

4、策略模式優缺點

優點

1)避免使用多重條件判斷

如果沒有策略模式,一個策略家族有多個策略算法,一會要使用A策略,一會要使用B策略,怎么設計呢?使用多重if的條件語句?多重條件語句不易維護,而且出錯的概率大大增強。使用策略模式后,簡化了操作,同時避免了條件語句判斷。

2)擴展性良好

在現有的系統中增加一個策略太容易了只要實現接口就可以,其他都不用修改,類似於一個可反復拆卸的插件,這大大地符合了OCP原則。

缺點

1)策略類數量增多

策略模式一個明顯的缺點是當備用行為過多時,行為對象會非常龐大

5、策略模式運用場景

通過上面的優缺點我們可以很好的去思考,什么場景下可以考慮用策略模式?

我的理解就是:每個if判斷都可以理解為就是一個策略。按理說都可以采用策略模式,但是如果if else里面的邏輯不多,且復用性很低,那就不需要。如果if里面的行為比較大而且這些行為復用性比較高就可以考慮通過采用策略模式。

在我們生活中比較常見的應用模式有:

1、電商網站支付方式,一般分為銀聯、微信、支付寶,可以采用策略模式
2、電商網站活動方式,一般分為滿減送、限時折扣、包郵活動,拼團等可以采用策略模式

二、策略模式實戰示例

最近正在做到電商項目,因為有多個活動,所以我就考慮用策略模式來實現。我們活動分為很多種滿減送,包郵活動,限時折扣等等,這里大致寫下對於多個活動如何去使用策略模式。

1、Order實體

活動是跟訂單綁定在一起的,只有下了單才去計算這個訂單走了哪個活動。

/**
  * @Description: 訂單實體
  */
public class Order {
    /**
     * 用戶ID
     */
    private Long userId;
    /**
     * 訂單編號
     */
    private String orderNumber;
    /**
     * 購買數量
     */
    private Integer goodsNumber;
    /**
     * 訂單運費
     */
    private Double orderFreight;
    /**
     * 訂單總價(訂單價格 + 運費)
     */
    private Double orderPrice;
    /**
     * 活動類型 1、包郵 2、滿減送 3、限時折扣
     */
    private String activityType;
    /**
     * 活動ID
     */
    private String activityId;
  //省略get set方法

2、ActivityTypeEnum活動枚舉

/**
 *  當前活動的枚舉
 */
@NoArgsConstructor
@AllArgsConstructor
public enum ActivityTypeEnum {

    FREE_SHIPPING(1, "包郵活動"),

    FULL_DELIVERY(2, "滿減活動"),

    LIMIT_DISCOUNT(3, "現實折扣");

    private Integer code;

    private String name;

    /**
     * 通過code獲取枚舉信息
     */
    public static ActivityTypeEnum decode(int code) {
        ActivityTypeEnum activity = null;
        for (ActivityTypeEnum type : ActivityTypeEnum.values()) {
            if (type.code==code) {
                activity = type;
                break;
            }
        }
        return activity;
    }
//省略get set方法
}

3、ActivityStrategy

活動策略接口

/**
 * 定義一個總的活動抽象
 */
public interface ActivityStrategy {

    /**
      * 屬於哪種活動
      */
     ActivityTypeEnum getActivityType();
 
     /**
      * 定義一個我們優惠活動的價格算法方法
      */
     Order calculate(Order order);
}

4、具體活動策略實現類

FreeShippingActivity 包郵

/**
  * @Description: 包郵活動
  */
@Service
public class FreeShippingActivity implements ActivityStrategy {

    @Override
    public ActivityTypeEnum getActivityType() {
        //返回包郵活動
        return ActivityTypeEnum.FREE_SHIPPING;
    }

    @Override
    public Order calculate(Order order) {
        //包郵活動是一個大的主題 ,里面可以創建很多小活動 比如價格滿100包郵活動,或者滿2件以上包郵活動,江浙滬包郵活動等等
        //如果這里通過活動ID獲取用戶具體選擇了哪一個活動。
        String activityId = order.getActivityId();
        //查詢數據庫
        System.out.println("模擬查詢數據庫 ,當前的活動是滿100包郵");
        order.setOrderFreight(0.0D);
        return order;
    }
}

FullDeliveryActivity (滿減送活動)

/**
  * @Description: 滿減送活動
  */
@Service
public class FullDeliveryActivity implements ActivityStrategy {
    @Override
    public ActivityTypeEnum getActivityType() {
        //滿減活動
        return ActivityTypeEnum.FULL_DELIVERY;
    }

    @Override
    public Order calculate(Order order) {
        //如果這里通過活動ID獲取用戶具體選擇了哪一個活動。
        String activityId = order.getActivityId();
        //查詢數據庫
        System.out.println("模擬查詢數據庫 ,當前的活動是滿100減20的");
        if (order.getOrderPrice() > 100) {
            order.setOrderPrice(order.getOrderPrice() - 20);
        }
        return order;
    }
}

LimitDiscountActivity (限時折扣活動)

/**
  * @Description: 限時折扣活動
  */
@Service
public class LimitDiscountActivity implements ActivityStrategy {
    @Override
    public ActivityTypeEnum getActivityType() {
        //返回限時活動枚舉
        return ActivityTypeEnum.LIMIT_DISCOUNT;
    }

    @Override
    public Order calculate(Order order) {
        //如果這里通過活動ID獲取用戶具體選擇了哪一個活動。
        String activityId = order.getActivityId();
        //查詢數據庫
        System.out.println("模擬查詢數據庫 ,當前的活動是截至2020.10.1前 打9折");
        order.setOrderPrice(order.getOrderPrice() * 0.9);

        return order;
    }
}

5、環境類

/**
 * @Description: 通過spring容器來管理該策略 負責和具體的策略類交互 動態的切換不同的算法
 */
@Component
public class ActivityContext  implements ApplicationContextAware {

    /**
     * 將策略存放在map中
     */
    private Map<ActivityTypeEnum, ActivityStrategy> activityStrategyMap = new ConcurrentHashMap<>();

    /**
     * 獲取對應策略
     */
    public Order resolveFile(ActivityTypeEnum activityTypeEnum, Order order) {
        ActivityStrategy activityStrategy = activityStrategyMap.get(activityTypeEnum);
        if (activityStrategy != null) {
           return activityStrategy.calculate(order);
        }
        return order;
    }
    
    /**
     * 把不同策略放到map
     */ 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //獲取ActivityStrategy接口所有的實現
        Map<String, ActivityStrategy> tmepMap = applicationContext.getBeansOfType(ActivityStrategy.class);
        //存入上面的map中
        tmepMap.values().forEach(strategyService -> activityStrategyMap.put(strategyService.getActivityType(), strategyService));
    }
}

4、測試類

@RestController
public class PayController {

    @Autowired
    private ActivityContext activityContext;

    @RequestMapping("/test")
    public  Object test(){
        Order order = new Order();
        //1 代表包郵
        order.setActivityType("1");
        //具體活動ID
        order.setActivityId("12");
        //總價
        order.setOrderPrice(200D);
        //運費
        order.setOrderFreight(10D);
       return activityContext.resolveFile(ActivityTypeEnum.decode(order.getActivityType()),order);
    }
}
//輸出: 模擬查詢數據庫 ,當前的活動是包郵

總結 這里我們沒有用到if else來判斷用戶到底選擇了哪個活動類型,而是通過先把所有活動的bean實體裝入一個map中,然后通過activityType 來獲取具體是哪個活動類型。以后新添加一個活動,比如拼團活動,我們只需做兩步

1、新建一個拼團活動策略類 實現總策略接口 2、ActivityTypeEnum中添加這個活動。

這里只是列了個架構,實際開發中比這個復雜多了,因為可以同時選擇多個活動,活動於活動之間又會有互斥關系。


參考

1、策略模式
2、支付平台選擇(策略模式)
3、Java設計模式-策略模式



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM