java策略模式典型案例 - 掘金

以一个顾客价格计算策略为背景,写一个策略模式的demo参考代码 : github.com/zhang-xiaox…

日常碰到的业务概述

登录类型,支付类型,供应商渠道,不同等级会员享受的优惠券价格不一样,等等业务判断,大量if else导致拓展(侧重新增)极其困难,维护(侧重修改)自然是改起来头痛(其实一个类型的增加[拓展一个类型]往往对应这个类型的增删改查CRUD[维护]),比如业务一开始一个简单的登录,往往做一个电话号码和验证码登录的方式或者账号密码登录方式,后来随着业务的增加或者提高用户体验,那么需要拓展(新增一种)三方登录,比如新增微信登录,支付宝登录,甚至抖音登录,一大堆,你要根据登录方式来处理,那就有点恼火吧,支付方式也是同样的问题,我们可以发现一个规律,凡是可以枚举的业务,往往都需要使用设计模式才能更好的解决,比如策略模式(往往搭配工厂模式使用更配哦),水来土掩,兵来将挡,这思想和高中数学中的分类讨论思想一模一样.遇事不要慌,因为还有dang中央.所以,我们就打个栗子,举个比方,更加形象一点,

没有用策略模式

我们一般是下面的写法,直接写一个类,在类里面直接写策略算法(功能实现)

//package com.demo.strategy;

/**
 * NoStrategy:没有策略的做法
 * 实现起来比较容易,符合一般开发人员的思路
 * 假如,类型特别多,算法比较复杂时,整个条件语句的代码就变得很长,难于维护。
 * 如果有新增类型,就需要频繁的修改此处的代码!
 * 不符合开闭原则!---对这个类的修改要关闭,就是这个类要是写好了就不要去改他了,对类的功能的拓展要开放,显然只有面向接口编程才满足,
 * 所以策略模式Strategy这个接口(文中涉及到的)就应运而生了晒哈哈哈
 * @author zhangxiaoxiang
 * @date: 2019/05/24
 */
public class NoStrategy {
    /**
     * 传入客服等级类型获取相应的价格
     * @param type   会员类型(等级)
     * @param price  响应的价格
     * @return
     */
    public double getPrice(String type, double price) {

        if ("普通客户小批量".equals(type)) {
            System.out.println("[未采用设计模式] 不打折,原价");
            return price;
        } else if ("普通客户大批量".equals(type)) {
            System.out.println("[未采用设计模式] 打九折");
            return price * 0.9;
        } else if ("老客户小批量".equals(type)) {
            System.out.println("[未采用设计模式] 打八折");
            return price * 0.8;
        } else if ("老客户大批量".equals(type)) {
            System.out.println("[未采用设计模式] 打七折");
            return price * 0.7;


        //拓展一种策略
        }else if("老客户特大批量".equals(type)){
            System.out.println("[未采用设计模式] 打六折");
            return price*0.6;
        }


        //乱传的也是当普通客户小批量(就是不打折)
        return price;
    }

}
复制代码

策略模式

看到上面的类,貌似想到一句话是不是,面向接口编程,上来就应该先是一个接口,所以改成接口

1:面向接口编程,策略模式也是一样,上来先来个策略接口压压惊(领导先开会,把任务指明,通过策略获取价格)

package com.demo.strategy;

/**
 * Strategy:策略接口
 * 这个是对类NoStrategy改成面向接口的方式实现策略,不要像NoStrategy一样,
 * 直接写死策略的实现,而是使用这个接口先定义策略,功能实现后面再说
 * @author zhangxiaoxiang
 * @date 2019/5/24
 */
public interface Strategy {
    /**
     * 通过策略获取价格
     * @param standardPrice
     * @return
     */
     double getPrice(double standardPrice);
}
复制代码

2:面向接口,组合编程,少用继承(继承虽然可以复用代码,但是会造成耦合度增加,解决方式往往采用接口做类的属性),如下,这样所有实现Strategy 的各种策略实现类都"组合"到这个类里面了,是不是所谓的高内聚啊

//package com.demo.strategy;
/**
 * Context:策略模式上下文---策略接收器,专门接收策略实现的算法
 * 负责和具体的策略类交互
 * 这样的话,具体的算法和直接的客户端调用分离了,使得算法可以独立于客户端独立的变化。
 * 如果使用spring的依赖注入功能,还可以通过配置文件,动态的注入不同策略对象,动态的切换不同的算法.
 * @author zhangxiaoxiang
 * @date: 2019/05/24
 */
public class Context {
    /**
     * 当前采用的算法对象
     * 面向接口,组合编程,少用继承
     * 简言之复杂类型(类,接口等)做属性
     */
    private Strategy strategy;

    /**
     * 选择策略Strategy实现类
     * 有参构造器(不写无参构造器,那么new 策略实现保证必须传一种策略,这里set方法也不用设置,
     * 设置了也没用(要设置set方法那么还是把无参构造也写出来才会有用,所以set伴随无参构造的感觉)
     * 这样同时也知道了为什么有参构造器设置了为什么无参构造器就失效了,JDK这样设计是有一定道理的,哈哈)
     * ---总之set注入也行,而且也推荐,也是一种组合/聚合的形式,只是这个例子采用构造器而已
     * @param strategy
     */
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public double getReultPrice(double price){
        return this.strategy.getPrice(price);
    }
    //我的例子没有使用set方式注入而已,也可以使用它哈
    // public void setStrategy(Strategy strategy) {
    //     this.strategy = strategy;
    // }
}
复制代码

3:既然是策略模式接口Strategy都明确了要做的事情是根据会员情况,返回价格,但是没有真正的实现,那么总有类来实现赛

策略实现类1    VIP0Strategy 

//package com.demo.strategy;
/**
 * VIP0Strategy:普通会员策略
 *
 * @author zhangxiaoxiang
 * @date: 2019/05/24
 */
public class VIP0Strategy implements Strategy {
    /**
     * 输入一个价格,经过VIP0Strategy策略计算价格
     * @param standardPrice
     * @return
     */
    @Override
    public double getPrice(double standardPrice) {
        System.out.println("[策略模式]普通会员 原价:"+standardPrice);
        return standardPrice;
    }

}
复制代码

 策略实现类2    VIP1Strategy 

package com.demo.strategy;

/**
 * VIP1Strategy: 一级会员策略
 *
 * @author zhangxiaoxiang
 * @date 2019/5/24
 */
public class VIP1Strategy implements Strategy {
    /**
     * 输入一个价格,经过VIP1Strategy策略计算价格
     * @param standardPrice
     * @return
     */
    @Override
    public double getPrice(double standardPrice) {
        System.out.println("[策略模式]一级会员 打九折:"+standardPrice * 0.9);
        return standardPrice * 0.9;
    }

}
复制代码

策略实现类3    VIP1Strategy 

package com.demo.strategy;
/**
 * VIP2Strategy:二级会员策略
 * @author zhangxiaoxiang
 * @date 2019/5/24
 */
public class VIP2Strategy implements Strategy {
    /**
     * 输入一个价格,经过VIP2Strategy策略计算价格
     * @param standardPrice
     * @return
     */
    @Override
    public double getPrice(double standardPrice) {
        System.out.println("[策略模式]二级会员八折:"+standardPrice*0.8);
        return standardPrice*0.8;
    }

}
复制代码

 类似的策略实现类N ........随意拓展策略就是了

 客户端(使用者)使用----->后面加粗这段话可以忽略,也可以思考 客户端可以理解为前端传到后端的controller里面来了,我们设想,加入前端传入的需求(比如"普通客户大批量", 1000),那么仍然需要if else判断,因为这里需要选择策略,还是逃不掉选择困难症,如果已知前端的参数,那么这个策略也就知道了,就逃开if else的魔爪了,这也是策略模式的弊端(不过把所有策略通过IOC注入到一个map,然后使用get(入参),这样也是一种解决方式,不过不太优雅,反射+枚举估计是个不错的选择,老衲没有尝试的,有空再试),需要知道策略才行,不知道策略是谁,就不知道new 那个VIPNStrategy(),你仔细想一想是不是这个逻辑.至于new 策略可以使用spring 的IOC容器去做

//package com.demo.strategy;

/**
 * Client:策略模式客户端---Client 的main方法 可以想象成我们在使用别人写好的框架,我们有新的需求,对框架开发者来说就是需要对已有的
 * 代码进行维护升级,比如此时我们修改NoStrategy类,那么修改完后新版本的框架NoStrategy类很有能是对已经在使用的客户机制上不兼容的,如果
 * 用户升级为新版框架,遇到使用NoStrategy类的会报错,各种不兼容就不符合开发者维护的版本的规范,所以修改已有的类是极其不科学的
 *
 *
 * @author zhangxiaoxiang
 * @date: 2019/05/24
 */
public class Client {
    public static void main(String[] args) {
        //System.out.println("------------------假如这里是service实现类,那么不知道使用哪种策略的就还是要根据参数new(或者spring IOC去取),感觉还是没有逃脱if else(所以结合工厂模式最佳啦,这个大伙可以思考一下,有空再更新这个博客)-----------------------");
        System.out.println("未使用模式-----------------------------------------");
        NoStrategy noStrategy=new NoStrategy();
        double price = noStrategy.getPrice("普通客户大批量", 1000);
        System.out.println(price);
        System.out.println("\n测试策略------------------------------------------");
        Context context0 = new Context(new VIP1Strategy());
        double resultPrice = context0.getReultPrice(1000);
        System.out.println(resultPrice);


        System.out.println("\n---怎么体现策略模式呢?比如现在需求是增加一种会员机制,  '老客户特大批量'  ,\n那么显然打折力度更大," +
                "我们设置为6折,分别在未使用策略模式和使用了策略模式的基础上拓展,看那个更加易于拓展,方便维护---\n");

        //首先这这里,作为客户端只能够传入 "老客户特大批量" 和价格1000元,但是计算代码再服务器NoStrategy类里面,如果不去修改服务器NoStrategy
        //    那么这里是无法实现的,策略模式也是一样的,那么回到服务器端思考,不用设计模式就要修改NoStrategy里面的if else之类的代码,使用策略模式
        //    就要增加新的策略实现,其实差不太多

        //新增策略后未使用模式(会修该策略核心类)
        NoStrategy noStrategy1=new NoStrategy();
        double price1 = noStrategy1.getPrice("老客户特大批量", 1000);
        System.out.println(price1);



        //新增策略后使用模式(不会修改策略接口,只是添加一个实现)
        Context context2=new Context(new VPI4Strategy()) ;
        double price2 = context2.getReultPrice(1000);
        System.out.println(price2);


        System.out.println("\n结论:修改服务器端已经写好了的类是极其不好的维护形式,因为这个类NoStrategy" +
                "\n可能在别的类中作为依赖或者叫做别的类引用了该类,在不明确的情况下,可能牵一发动全身,是不好的维护方式,使用了策略模式," +
                "\n我们对只是添加了一个策略接口的实现,低侵入式,不会对已有代码造成影响,低耦合");


    }
}
复制代码

使用模式的成果

输出结果

 

小结和抛出一些观点:上面基本已经完成策略模式的应用了,当然缺点就是调用端需要对策略有哪些熟悉,不知道的话就不能传入策略,所以这是最大的缺点.其实java的JDK8的函数式编程和Lambda表达式(简化匿名类等写法)可以让策略模式更加优雅,其实就是相当于JDK8新特性是把23中设计模式更加抽象的方式用在新语法上了,符合时代潮流,拓展java的函数式编程领域,可以大概参考哈新特性  zhangxiaoxiang.blog.csdn.net/article/det…


原网址: 访问
创建于: 2023-06-12 12:00:46
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论