这里举一个我们有购买一个商品,我将用户分为普通用户 、vip用户、超级vip用户。普通用户9.5折,vip用户9折,超级vip用户8折 这种方式来计算用户价格,我们写一下对应的业务代码。
1、定义一下用户类型
public enum UserTypeEnum {
/**
* 普通用户
*/
COMMON_USER(0),
/**
* VIP用户
*/
VIP_USER(1),
/**
* 超级VIP用户
*/
SUPER_VIP_USER(2);
private int type;
UserTypeEnum(int type) {
this.type = type;
}
public int getType() {
return type;
}
public static UserTypeEnum getUserEnum(int type) {
for (UserTypeEnum userTypeEnum : UserTypeEnum.values()) {
if (userTypeEnum.type == type) {
return userTypeEnum;
}
}
return null;
}
}
2、计算价格方法
private static final String NO_USER_TYPE_MSG = "该用户类型没有对应策略";
public BigDecimal quotePrice(BigDecimal orderPrice, int type) {
UserTypeEnum userEnum = UserTypeEnum.getUserEnum(type);
if (Objects.isNull(userEnum)) {
throw new IllegalArgumentException(NO_USER_TYPE_MSG);
}
BigDecimal price;
switch (userEnum) {
case COMMON_USER:
price = orderPrice.multiply(new BigDecimal(0.95));
break;
case VIP_USER:
price = orderPrice.multiply(new BigDecimal(0.9));
break;
case SUPER_VIP_USER:
price = orderPrice.multiply(new BigDecimal(0.8));
break;
default:
price = new BigDecimal(-1);
}
return price;
}
其实从这段代码里面,看起来还好,因为每种用户只有一次处理,用户的种类也非常有限。如果是很多确定好的简单方法,这么做并没有什么问题。但我们如果这个计算规则经常变动,可能后续加入积分,优惠券,购买总金额。。等等这些需求的时候。如果一开始采用中方式,大概率是在这个类里面写了很多私有方法。这个类变得越来越臃肿,去改这个方法里面代码需要重新读这个方法里面所有代码,后续越来越难已维护。
策略模式定义:
定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
实际项目开发中,会有些类仅需要依靠行为就可以区分开来,这时候我们便可以利用封装,使用的时候按实际情况替换即可。
我们在工作中一般是分为几种情况,每种情况建立一个策略类,在用一个上下文将这几种策略和进行统一管理,分为哪种情况直接采用那种策略。
1、类型定义用户枚举类(由于在背景介绍已经贴出来了,这里不再贴出)
2、定义工厂管理类
public class UserPayStrategyFactory {
private static Map userPayBeanMap = new ConcurrentHashMap();
public static UserPayService getByUserType(Integer type){
return userPayBeanMap.get(type);
}
public static void register(Integer userType,UserPayService userPayService){
Assert.notNull(userType,"type can't be null");
userPayBeanMap.put(userType,userPayService);
}
}
3、定义接口
public interface UserPayService {
/**
* 计算应付价格
*/
BigDecimal quotePrice(BigDecimal orderPrice);
}
4、定义三种策略具体的实现类
这里是通过InitializingBean方法钩子方法,将对应该类注册到之前定义的工厂管理类中
/**
* 普通用户的实现
*/
@Service
public class CommonUserPayServiceRegisterImpl implements UserPayService,InitializingBean {
@Override
public BigDecimal quotePrice(BigDecimal orderPrice) {
return orderPrice.multiply(new BigDecimal(0.95));
}
@Override
public void afterPropertiesSet() {
UserPayStrategyFactory.register(UserTypeEnum.COMMON_USER.getType(),this);
}
}
/**
* vip接口实现
*/
public class VipUserPayServiceRegisterImpl implements UserPayService,InitializingBean {
@Override
public BigDecimal quotePrice(BigDecimal orderPrice) {
return orderPrice.multiply(new BigDecimal(0.9));
}
@Override
public void afterPropertiesSet() {
UserPayStrategyFactory.register(UserTypeEnum.VIP_USER.getType(),this);
}
}
/**
* 超级vip接口实现
*/
@Service
public class SuperVipUserPayServiceRegisterImpl implements UserPayService,InitializingBean {
@Override
public BigDecimal quotePrice(BigDecimal orderPrice) {
return orderPrice.multiply(new BigDecimal(0.8));
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayStrategyFactory.register(UserTypeEnum.SUPER_VIP_USER.getType(),this);
}
}
5、定义具体应用
@GetMapping("/getUserQuotePriceRegister")
public String getUserQuotePriceRegister(HttpServletRequest request, HttpServletResponse response) throws IOException {
UserPayService userService = UserPayStrategyFactory.getByUserType(UserTypeEnum.COMMON_USER.getType());
BigDecimal quote = userService.quotePrice(new BigDecimal(300));
System.out.println("普通用户商品的最终价格为_register:" + quote.doubleValue());
UserPayService userService2 = UserPayStrategyFactory.getByUserType(UserTypeEnum.VIP_USER.getType());
BigDecimal quote2 = userService2.quotePrice(new BigDecimal(300));
System.out.println("vip会员商品的最终价格为_register:" + quote2.doubleValue());
UserPayService userService3 = UserPayStrategyFactory.getByUserType(UserTypeEnum.SUPER_VIP_USER.getType());
BigDecimal quote3 = userService3.quotePrice(new BigDecimal(300));
System.out.println("SuperVip会员商品的最终价格为_register:" + quote3.doubleValue());
return "success";
}
1、定义注解方法
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//如果父类实现该注解,子类也将获得该注解
@Inherited
public @interface UserType {
UserTypeEnum value();
}
2、类型定义用户枚举类(由于在背景介绍已经贴出来了,这里不再贴出)
3、定义上下文管理bean的工厂
@Component
public class UserTypeContext implements ApplicationContextAware {
private static final String NO_USER_TYPE_MSG = "该用户类型没有对应策略";
private static Map> userPayBeanMap = new HashMap<>();
@Resource
private ApplicationContext applicationContext;
/**
* 将实现 有UserType 注解,实现UserPayService 接口的类放到userPayBeanMap
* @param applicationContext 上下文
*
*/
@Override
public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
Map userTypeMap = applicationContext.getBeansWithAnnotation(UserType.class);
userTypeMap.forEach((k,v)->{
if(v instanceof UserPayService){
Class extends UserPayService> payServiceClass = ((UserPayService) v).getClass();
userPayBeanMap.put(payServiceClass.getAnnotation(UserType.class).value().getType(), payServiceClass);
}
});
}
/**
* 通过用户类型
* @param type 用户类型
* @return 用户类型实现类
*/
public UserPayService getUserService(Integer type) {
Class extends UserPayService> userPayClass = userPayBeanMap.get(type);
if (Objects.isNull(userPayClass)) {
throw new IllegalArgumentException(NO_USER_TYPE_MSG);
}
return applicationContext.getBean(userPayClass);
}
}
4、定义接口
public interface UserPayService {
/**
* 计算应付价格
*/
BigDecimal quotePrice(BigDecimal orderPrice);
}
5、定义三种策略具体的实现类
这里是通过打上注解,来标注该策略的类型
/**
* 普通用户接口实现
*/
@UserType(UserTypeEnum.COMMON_USER)
@Service
public class CommonUserPayServiceImpl implements UserPayService {
@Override
public BigDecimal quotePrice(BigDecimal orderPrice) {
return orderPrice.multiply(new BigDecimal(0.95));
}
}
/**
* vip接口实现
*/
@UserType(UserTypeEnum.VIP_USER)
@Service
public class VipUserPayServiceImpl implements UserPayService {
@Override
public BigDecimal quotePrice(BigDecimal orderPrice) {
return orderPrice.multiply(new BigDecimal(0.9));
}
}
/**
* 超级 vip接口实现
*/
@UserType(UserTypeEnum.SUPER_VIP_USER)
@Service
public class SuperVipUserPayServiceImpl implements UserPayService {
@Override
public BigDecimal quotePrice(BigDecimal orderPrice) {
return orderPrice.multiply(new BigDecimal(0.8));
}
}
6、定义具体应用
@GetMapping("/getUserQuotePrice")
public String getUserQuotePrice(HttpServletRequest request, HttpServletResponse response) throws IOException {
UserPayService userService = userTypeContext.getUserService(UserTypeEnum.COMMON_USER.getType());
BigDecimal quote = userService.quotePrice(new BigDecimal(300));
System.out.println("普通用户商品的最终价格为:" + quote.doubleValue());
UserPayService userService2 = userTypeContext.getUserService(UserTypeEnum.VIP_USER.getType());
BigDecimal quote2 = userService2.quotePrice(new BigDecimal(300));
System.out.println("vip会员商品的最终价格为:" + quote2.doubleValue());
UserPayService userService3 = userTypeContext.getUserService(UserTypeEnum.SUPER_VIP_USER.getType());
BigDecimal quote3 = userService3.quotePrice(new BigDecimal(300));
System.out.println("SuperVip会员商品的最终价格为:" + quote3.doubleValue());
return "success";
}
两种方案都是都有一个管理他们本身bean的上下文,一种是用InitializingBean的钩子方法自己注册到factory中。另外一种通过ApplicationContextAware的钩子方法,将实现该注解的bean全部注册自己管理的类中,第二种在我们自己实现策略的时候更加方便,只需要增加一个注解就可以了。第一种需要自己实现InitializingBean将其自身注入进factory中。