策略模式
一, 概念
1, 理解策略模式
策略模式是一种行为型模式, 它将对象和行为分开, 将行为定义为 一个行为接口 和 具体行为的实现. 策略模式最大的特点是行为的变化, 行为之间可以相互替换.
每个 if 判断都可以理解为就是一个策略.
2, 策略模式特点
策略模式把对象本身和行为区分开来, 因此我们整个模式也分为三个部分.
, 抽象策略类 (Strategy): 策略的抽象, 行为方式的抽象
, 具体策略类 (ConcreteStrategy): 具体的策略实现, 每一种行为方式的具体实现.
, 环境类 (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,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,ActivityStrategy(活动策略接口)
- /**
- * 定义一个总的活动抽象
- */
- public interface ActivityStrategy {
- /**
- * 定义一个我们优惠活动的价格算法方法
- */
- Order calculate (Order order);
- }
3, 具体活动策略实现类
FreeShippingActivity 包邮
- /**
- * @Description: 包邮活动
- */
- @Component
- @Service("freeShippingActivity")
- public class FreeShippingActivity implements ActivityStrategy {
- @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: 满减送活动
- */
- @Component
- @Service("fullDeliveryActivity")
- public class FullDeliveryActivity implements ActivityStrategy {
- @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: 限时折扣活动
- */
- @Component
- @Service("limitDiscountActivity")
- public class LimitDiscountActivity implements ActivityStrategy {
- @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;
- }
- }
3, 环境类
- /**
- * 负责和具体的策略类交互 动态的切换不同的算法
- */
- @Component
- public class ActivityContext {
- @Autowired
- private FreeShippingActivity freeShippingActivity;
- @Autowired
- FullDeliveryActivity fullDeliveryActivity;
- @Autowired
- LimitDiscountActivity limitDiscountActivity;
- /**
- * 出行策略接口
- */
- private final Map<String, ActivityStrategy> activityStrategyMap = new HashMap();
- /**
- * 初始化 把这几个活动的示例 初始化的时候就装到一个 map 集合中
- */
- @PostConstruct
- public void init() {
- //1, 包邮 2, 满减送 3, 限时折扣
- activityStrategyMap.put("1", freeShippingActivity);
- activityStrategyMap.put("2", fullDeliveryActivity);
- activityStrategyMap.put("3", limitDiscountActivity);
- }
- /**
- * 计算价格
- */
- public Order calculate(Order order) {
- ActivityStrategy activityStrategy = activityStrategyMap.get(order.getActivityType());
- return activityStrategy.calculate(order);
- }
- }
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.calculate(order);
- }
- }
- // 输出: 模拟查询数据库 , 当前的活动是包邮
总结 这里我们没有用到 if else 来判断用户到底选择了哪个活动类型, 而是通过先把所有活动的 bean 实体装入一个 map 中, 然后通过 activityType 来获取具体是哪个活动类型.
以后新添加一个活动, 比如拼团活动, 我们只需做两步
1, 新建一个拼团活动策略类 实现总策略接口 2,activityStrategyMap 中 put 这个折扣活动的 bean 实体.
除了 map 管理 bean 外, 还有一种方式就是可以通过 spring 来管理这些具体的策略类
- //freeShippingActivity 这个值可以在数据库添加一张表来管理, 然后来读取 这样的话新建活动只需要做上面的第一步, 代码更加好维护了.
- ActivityStrategy payStrategy= SpringUtils.getBean("freeShippingActivity", ActivityStrategy.class);
这里只是列了个架构, 实际开发中比这个复杂多了, 因为可以同时选择多个活动, 活动于活动之间又会有互斥关系.
参考
1, 策略模式 https://github.com/pengMaster/strategyMode
2, 支付平台选择 (策略模式)
3,Java 设计模式 - 策略模式 https://segmentfault.com/a/1190000019625254
``` 别人骂我胖, 我会生气, 因为我心里承认了我胖. 别人说我矮, 我就会觉得好笑, 因为我心里知道我不可能矮. 这就是我们为什么会对别人的攻击生气. 攻我盾者, 乃我内心之矛 (8). ```
来源: https://www.cnblogs.com/qdhxhz/p/12359523.html