策略模式屬於對象的行爲模式。其用意是針對一組算法,將每一個算法封裝到具有共同接口的獨立的類中,從而使得它們可以相互替換。策略模式使得算法可以在不影響到客戶端的情況下發生變化。
在系統中,常常需要根據不同的用戶類型或者屬性進行相應的處理,常見做法,需要對處理方式進行抽象,同時根據不同的類型或者屬性來指定對應的實現類來進行處理。
假設現在要設計一個電子商務網站的購物車系統。本網站可能對所有的高級會員7折折扣;對中級會員8折折扣;對初級會員9折折扣,普通用戶沒有折扣。
抽象一個計算價格的接口
public interface PriceStrategy {
double calculatePrice(double fee);
}
高級vip
public class SeniorVIPStrategy implements PriceStrategy {
@Override
public double calculatePrice(double fee) {
return fee * 0.7;
}
}
中級vip
public class IntermediateVIPStrategy implements PriceStrategy {
@Override
public double calculatePrice(double fee) {
return fee * 0.8;
}
}
低級VIP
public class InitialVIPStrategy implements PriceStrategy {
@Override
public double calculatePrice(double fee) {
return fee * 0.9;
}
}
普通用戶
public class UserStrategy implements PriceStrategy {
@Override
public double calculatePrice(double fee) {
return fee;
}
}
費用類
public class Charge {
private PriceStrategy strategy;
public Charge(PriceStrategy strategy) {
this.strategy = strategy;
}
public double price(double fee) {
return strategy.calculatePrice(fee);
}
}
接下來在業務代碼中使用
public double charge(User user, double cost) {
Charge charge;
double cost = 100.0;
switch(user.getUserType()) {
case 1:
charge = new Charge(new SeniorVIPStrategy());
break;
case 2:
charge = new Charge(new IntermediateVIPStrategy());
break;
case 3:
charge = new Charge(new InitialVIPStrategy());
break;
case 4:
charge = new Charge(new UserStrategy());
break;
default:
throw new RuntimeException("不支持的用戶類型");
}
return charge.price(cost);
}
這樣導致代碼裏面有大量的判斷存在,如果有新的用戶類型及對應的計算規則,需要修改這部分的代碼 ,繁瑣異常。
接下來利用SpringBoot對上述流程進行些微的改造:
1.定義一個註解,用來標註用戶類型
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
//@Inherited //如果需要事務處理,需要加上這個註解
public @interface UserTypeAnnotation {
int userType() default 0;
}
2.同上面的流程一樣,定義一系列的價格策略計算類簇,但是注意,需要在每個實現類加上上面自定義的註解,並配置上對應的值
@Component
@UserTypeAnnotation(userType = 1)
//高級會員
public class SeniorVIPStrategy implements PriceStrategy {
public double calculatePrice(double cost) {
return cost * 0.7;
}
}
@Component
@UserTypeAnnotation(userType = 2)
//中級會員
public class IntermediateVIPStrategy implements PriceStrategy {
public double calculatePrice(double cost) {
return cost * 0.8;
}
}
@Component
@UserTypeAnnotation(userType = 3)
//初級會員
public class InitialVIPStrategy implements PriceStrategy {
public double calculatePrice(double cost) {
return cost * 0.9;
}
}
@Component
@UserTypeAnnotation(userType = 4)
//普通用戶
public class UserStrategy implements PriceStrategy {
public double calculatePrice(double cost) {
return cost;
}
}
3.在容器啓動時,初始化一個map,用來存放userType與對應策略處理類的映射關係
@Component
public class StrategyFactory implements CommandLineRunner, ApplicationContextAware {
private volatile ApplicationContext applicationContext;
private static HashMap<Integer, PriceStrategy> userTypeStrategyMap;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void run(String... args) {
initUserTypeStrategyMap();
}
private void initUserTypeStrategyMap() {
Collection<PriceStrategy> userTypeStrategyList = this.applicationContext.getBeansOfType(PriceStrategy.class).values();
userTypeStrategyMap = new HashMap<>(userTypeStrategyList.size());
for (PriceStrategy strategy : userTypeStrategyList) {
Class<? extends PriceStrategy> aClass = strategy.getClass();
UserTypeAnnotation annotation = aClass.getAnnotation(UserTypeAnnotation.class);
if (annotation != null) {
userTypeStrategyMap.put(annotation.userType(), strategy);
}
}
}
//通過這個方法獲取對應的價格計算策略實現類
public static PriceStrategy getPriceStrategyByUserType(Integer userType) {
PriceStrategy strategy = userTypeStrategyMap.get(userType);
if (strategy == null) {
throw new RuntimeException("不支持的用戶類型");
}
return strategy;
}
}
4.調用
public double charge(User user, double cost) {
double cost = 100.0;
return StrategyFactory.getPriceStrategyByUserType(user.getUserType()).calculatePrice(cost);
}
這樣當有新的user_type及價格計算方式出現時,只需要實現PriceStrategy接口並在對應的實現類上面加入@Component和@UserTypeAnnotation註解即可,無需修改業務代碼 !