但是,真正在代码中使用,比如在一个 Web 项目中使用,上面这个 Demo 根本没办法直接用 。
首先,在 Web 项目中,上面我们创建出来的这些策略类都是被 Spring 托管的,我们不会自己去 New 一个实例出来 。
其次,在 Web 项目中,如果真要计算价格,也是要事先知道用户的会员等级,比如从数据库中查出会员等级,然后根据等级获取不同的策略类执行计算价格方法 。
那么,Web 项目中真正的计算价格的话,伪代码应该是这样的:
/** * @author mhcoding */ public BigDecimal calPrice(BigDecimal orderPrice,User user) { String vipType = user.getVipType(); if (vipType == 专属会员) { //伪代码:从Spring中获取超级会员的策略对象 UserPayService strategy = Spring.getBean(ParticularlyVipPayService.class); return strategy.quote(orderPrice); } if (vipType == 超级会员) { UserPayService strategy = Spring.getBean(SuperVipPayService.class); return strategy.quote(orderPrice); } if (vipType == 普通会员) { UserPayService strategy = Spring.getBean(VipPayService.class); return strategy.quote(orderPrice); } return 原价; } 通过以上代码,我们发现,代码可维护性和可读性好像是好了一些,但是好像并没有减少 if-else 啊 。
但是,策略模式的使用上,还是有一个比较大的缺点的:客户端必须知道所有的策略类,并自行决定使用哪一个策略类 。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类 。
也就是说,虽然在计算价格的时候没有 if-else 了,但是选择具体的策略的时候还是不可避免的还是要有一些 if-else 。
另外,上面的伪代码中,从 Spring 中获取会员的策略对象我们是伪代码实现的,那么代码到底该如何获取对应的 Bean 呢?
接下来我们看如何借助 Spring 和工厂模式,解决上面这些问题 。
工厂模式
为了方便我们从 Spring 中获取 UserPayService 的各个策略类,我们创建一个工厂类:
/** * @author mhcoding */ public class UserPayServiceStrategyFactory { private static Map<String,UserPayService> services = new ConcurrentHashMap<String,UserPayService>(); public static UserPayService getByUserType(String type){ return services.get(type); } public static void register(String userType,UserPayService userPayService){ Assert.notNull(userType,"userType can't be null"); services.put(userType,userPayService); } } 这个 UserPayServiceStrategyFactory 中定义了一个 Map,用来保存所有的策略类的实例,并提供一个 getByUserType 方法,可以根据类型直接获取对应的类的实例 。还有一个 Register 方法,这个后面再讲 。
有了这个工厂类之后,计算价格的代码即可得到大大的优化:
【刚来的大神彻底干掉了代码中的if else...】/** * @author mhcoding */ public BigDecimal calPrice(BigDecimal orderPrice,User user) { String vipType = user.getVipType(); UserPayService strategy = UserPayServiceStrategyFactory.getByUserType(vipType); return strategy.quote(orderPrice); } 以上代码中,不再需要 if-else 了,拿到用户的 vip 类型之后,直接通过工厂的 getByUserType 方法直接调用就可以了 。
通过策略+工厂,我们的代码很大程度的优化了,大大提升了可读性和可维护性 。
但是,上面还遗留了一个问题,那就是 UserPayServiceStrategyFactory 中用来保存所有的策略类的实例的 Map 是如何被初始化的?各个策略的实例对象如何塞进去的呢?
Spring Bean 的注册
还记得我们前面定义的 UserPayServiceStrategyFactory 中提供了的 Register 方法吗?他就是用来注册策略服务的 。
接下来,我们就想办法调用 Register 方法,把 Spring 通过 IOC 创建出来的 Bean 注册进去就行了 。
这种需求,可以借用 Spring 中提供的 InitializingBean 接口,这个接口为 Bean 提供了属性初始化后的处理方法 。
它只包括 afterPropertiesSet 方法,凡是继承该接口的类,在 Bean 的属性初始化后都会执行该方法 。
那么,我们将前面的各个策略类稍作改造即可:
/** * @author mhcoding */ @Service public class ParticularlyVipPayService implements UserPayService,InitializingBean { @Override public BigDecimal quote(BigDecimal orderPrice) { if (消费金额大于30元) { return 7折价格; } } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("ParticularlyVip",this); } } @Service public class SuperVipPayService implements UserPayService ,InitializingBean{ @Override public BigDecimal quote(BigDecimal orderPrice) { return 8折价格; } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("SuperVip",this); } } @Service public class VipPayService implements UserPayService,InitializingBean { @Override public BigDecimal quote(BigDecimal orderPrice) { if(该用户超级会员刚过期并且尚未使用过临时折扣){ 临时折扣使用次数更新(); returen 8折价格; } return 9折价格; } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("Vip",this); } }
推荐阅读
- MySQL中七个查询命令的优先级及特点
- 千万级MySQL数据库建立索引,提高性能的秘诀
- 开发者必须了解的22个常用小程序开发api接口
- Nginx 模块的基本结构
- 通过监控DNS记录,来保护网站的几种方式?你需要了解
- 细说HTTP协议及其工作流程
- 5 种用 CSS 隐藏页面元素的方法
- 瓷砖用什么擦会发亮,瓷砖脏的发黑怎么清理
- 什么是窗户的k值,门窗k值一般是多少
- 床单用法兰绒的好吗,婴儿能睡法兰绒床单吗
