zoukankan      html  css  js  c++  java
  • 【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、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、策略模式

    2、支付平台选择(策略模式)

    3、Java设计模式-策略模式



    别人骂我胖,我会生气,因为我心里承认了我胖。别人说我矮,我就会觉得好笑,因为我心里知道我不可能矮。这就是我们为什么会对别人的攻击生气。
    攻我盾者,乃我内心之矛(8)。
    
  • 相关阅读:
    linux安装ftp服务器
    批量删除某个用户所有的表
    解决ORA-14450:试图访问已经在使用的事务处理临时表
    jpa 表字段转bean对象
    oracle查询包含某个字段的表
    给所有的表建同义词
    easyui datagrid
    oracle 拼接一张表所有字段
    easyui js基础
    MySQL的Grant命令
  • 原文地址:https://www.cnblogs.com/qdhxhz/p/12961182.html
Copyright © 2011-2022 走看看