[01][01][16] 策略模式详解

1. 定义

策略模式:指定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式上算法的变化不会影响到使用算法的用户

可以避免多重分支的if...esleswitch语句

2. 适用场景

  • 1.假如系统中有很多类,而他们的区别仅仅在于他们的行为不同
  • 2.一个系统需要动态地在几种算法中选择一种

Spring 中以 Strategy 结尾的类都是策略模式的体现

3. 电商优惠策略实现

电商优惠策略会有很多种可能如:领取优惠券抵扣,返现促销,拼团优惠.下面我们用代码来模拟

  • 通过工厂模式+单例模式+策略模式的方式实现

  • 后面增加策略只需要增加对应的具体策略,在策略枚举中增加对应的策略名称,在工厂中增加对应的策略就能完成策略活动的增加,不需要修改过多的代码,对于用户而言是无感知的

  • 策略抽象类

public interface PromotionStrategy {
    /**
     * 优惠活动
     */
    void doPromotion();
}
  • 具体策略
/**
 * 返现活动
 */
public class CashBackStrategy implements PromotionStrategy {
    @Override
    public void doPromotion() {
        System.out.println("正在执行返现活动");
    }
}
/**
 * 优惠券活动
 */
public class CouponStrategy implements PromotionStrategy {
    @Override
    public void doPromotion() {
        System.out.println("领取优惠券,正在执行优惠券活动");
    }
}
/**
 * 无促销活动
 */
public class EmptyStrategy implements PromotionStrategy {

    @Override
    public void doPromotion() {
        System.out.println("无促销活动");
    }
}
  • 策略枚举
public enum PromotionEnum {
    /**
     * 返现
     */
    CASHBACK,
    /**
     * 优惠券
     */
    COUPON,
    /**
     * 无活动
     */
    EMPTY
}
  • 策略工厂
/**
 * 工厂模式 + 单例模式
 */
public class PromotionStrategyFactory {

    private static Map<Enum, PromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<>();

    static {
        PROMOTION_STRATEGY_MAP.put(PromotionEnum.CASHBACK, new CashBackStrategy());
        PROMOTION_STRATEGY_MAP.put(PromotionEnum.COUPON, new CouponStrategy());
        PROMOTION_STRATEGY_MAP.put(PromotionEnum.EMPTY, new EmptyStrategy());
    }

    private PromotionStrategyFactory() {}

    public static PromotionStrategy getPromotionStrategy (PromotionEnum promotionEnum) {
        return PROMOTION_STRATEGY_MAP.get(promotionEnum);
    }
}
  • 测试类
public class PromotionStrategyTest {
    public static void main(String[] args) {
        PromotionStrategy promotionStrategy = PromotionStrategyFactory.getPromotionStrategy(PromotionEnum.CASHBACK);
        promotionStrategy.doPromotion();
    }
}

运行结果

4. 支付策略实现

相信小伙伴们都用过支付宝,微信支付,银联支付以及京东白条.一个常见的应用场景就是大家在下单支付时会提示选择支付方式,如果用户未选,系统也会默认好推荐的支付方式进行结算

  • 创建支付状态的包装类 PayState
/**
 * 支付完成以后的状态
 */
public class PayState {
    private int code;
    private Object data;
    private String msg;

    public PayState(int code, String msg, Object data) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    public String toString() {
        return ("支付状态:[" + code + "]," + msg + ",交易详情:" + data);
    }
}
  • 创建 Payment 抽象类,定义支付规范和支付逻辑
/**
 * 支付渠道
 */
public abstract class Payment {
    //支付类型
    public abstract String getName();

    //查询余额
    protected abstract double queryBalance(String uid);

    //扣款支付
    public PayState pay(String uid, double amount) {
        if (queryBalance(uid) < amount) {
            return new PayState(500, "支付失败", "余额不足");
        }
        return new PayState(200, "支付成功", "支付金额:" + amount);
    }
}
  • 支付宝 AliPay 类
/**
 * 支付宝支付
 */
public class AliPay extends Payment {
    public String getName() {
        return "支付宝";
    }

    protected double queryBalance(String uid) {
        return 900;
    }
}
  • 京东白条 JDPay 类
/**
 * 京东白条
 */
public class JDPay extends Payment {
    public String getName() {
        return "京东白条";
    }

    protected double queryBalance(String uid) {
        return 500;
    }
}
  • 微信 WechatPay 类
/**
 * 微信支付
 */
public class WechatPay extends Payment {
    public String getName() {
        return "微信支付";
    }

    protected double queryBalance(String uid) {
        return 256;
    }
}
  • 银联 UnionPay 类
/**
 * 银联支付
 */
public class UnionPay extends Payment {
    public String getName() {
        return "银联支付";
    }

    protected double queryBalance(String uid) {
        return 120;
    }
}
  • 支付策略管理类
public class PayStrategy {
    public static final String ALI_PAY = "AliPay";
    public static final String JD_PAY = "JdPay";
    public static final String UNION_PAY = "UnionPay";
    public static final String WECHAT_PAY = "WechatPay";
    public static final String DEFAULT_PAY = ALI_PAY;

    private static Map<String, Payment> payStrategy = new HashMap<String, Payment>();

    static {
        payStrategy.put(ALI_PAY, new AliPay());
        payStrategy.put(WECHAT_PAY, new WechatPay());
        payStrategy.put(UNION_PAY, new UnionPay());
        payStrategy.put(JD_PAY, new JDPay());
    }

    public static Payment get(String payKey) {
        if (!payStrategy.containsKey(payKey)) {
            return payStrategy.get(DEFAULT_PAY);
        }
        return payStrategy.get(payKey);
    }
}
  • 订单 Order 类
/**
 * 订单类
 */
public class Order {
    private String uid;
    private String orderId;
    private double amount;

    public Order(String uid, String orderId, double amount) {
        this.uid = uid;
        this.orderId = orderId;
        this.amount = amount;
    }

    //完美地解决了 switch 的过程,不需要在代码逻辑中写 switch 了 //更不需要写 if else if
    public PayState pay() {
        return pay(PayStrategy.DEFAULT_PAY);
    }

    public PayState pay(String payKey) {
        Payment payment = PayStrategy.get(payKey);
        System.out.println("欢迎使用" + payment.getName());
        System.out.println("本次交易金额为:" + amount + ",开始扣款...");
        return payment.pay(uid, amount);
    }
}
  • 测试代码
public class PayStrategyTest {
    public static void main(String[] args) {
        //省略把商品添加到购物车,再从购物车下单
        //直接从点单开始
        Order order = new Order("1", "20180311001000009", 324.45);

        // 开始支付,选择微信支付、支付宝、银联卡、京东白条、财付通
        // 每个渠道它支付的具体算法是不一样的
        // 基本算法固定的

        // 这个值是在支付的时候才决定用哪个值
        System.out.println(order.pay(PayStrategy.ALI_PAY));
    }
}

运行结果

5. 源码分析

5.1 JDK 的 Comparator

比较常用的比较器 Comparator 接口,我们看到的一个大家常用的 compare()方法,就是一个策略抽象实现

public interface Comparator<T> {
   ...
   int compare(T o1, T o2);
   ...
}

Comparator 抽象下面有非常多的实现类,我们经常会把 Comparator 作为参数传入作为排序策略

  • Arrays 类的 parallelSort 方法

在 Arrays 中 parallelSort 是用来排序的,其中存在一个方法是可以自定义排序策略的,用户可以自己实现 Comparator 定义自己的排序策略

public class Arrays {
    public static <T> void parallelSort(T[] a, Comparator<? super T> cmp) {
        if (cmp == null)
            cmp = NaturalOrder.INSTANCE;
        int n = a.length, p, g;
        if (n <= MIN_ARRAY_SORT_GRAN ||
            (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            TimSort.sort(a, 0, n, cmp, null, 0, 0);
        else
            new ArraysParallelSortHelpers.FJObject.Sorter<T>
                (null, a,
                 (T[])Array.newInstance(a.getClass().getComponentType(), n),
                 0, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                 MIN_ARRAY_SORT_GRAN : g, cmp).invoke();
    }
}
  • TreeMap 的构造方法
public class TreeMap<K,V> extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, Serializable
{
    ...
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
    ...
}

5.2 Spring 的 Resource

Spring 的 Resource 类的实现类就是策略模式的体现,我们虽然没有直接使用 Resource 类,但是我们经常使用它的子类

public interface Resource extends InputStreamSource {
    boolean exists();
    default boolean isReadable() {
        return true;
    }

    default boolean isOpen() {
        return false;
    }

    default boolean isFile() {
        return false;
    }

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String var1) throws IOException;

    @Nullable
    String getFilename();

    String getDescription();
}

还有一个非常典型的场景,Spring 的初始化也采用了策略模式,不同的类型的类采用不同的初始化策略.首先有一个 InstantiationStrategy 接口,我们来看一下源码

public interface InstantiationStrategy {
    Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3) throws BeansException;

    Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3, Constructor<?> var4, Object... var5) throws BeansException;

    Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3, @Nullable Object var4, Method var5, Object... var6) throws BeansException;
}

顶层的策略抽象非常简单,但是它下面有两种策略 SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy,我们看一下类图

打开类图我们还发现 CglibSubclassingInstantiationStrategy 策略类还继承了 SimpleInstantiationStrategy 类,说明在实际应用中多种策略之间还可以继承使用

6. 优缺点

6.1 优点

  • 策略模式符合开闭原则
  • 避免使用多重条件转移语句,如 if…else 语句,switch 语句
  • 使用策略模式可以提高算法的保密性和安全性

6.2 缺点

  • 客户端必须知道所有的策略,并且自行决定使用哪一个策略
  • 代码中会产生非常多策略类,增加维护难度

7. 委派模式与策略模式综合应用

在上面的代码中我们列举了非常几个业务场景,相信小伙伴对委派模式和策略模式有了非常深刻的理解了.现在,我们再来回顾一下 DispatcherServlet 的委派逻辑,代码如下

public class DispatcherServlet extends HttpServlet{
    private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception{

        String uri = request.getRequestURI();
        String mid = request.getParameter("mid");

        if("getMemberById".equals(uri)){
            new MemberController().getMemberById(mid);
        }else if("getOrderById".equals(uri)){
            new OrderController().getOrderById(mid);
        }else if("logout".equals(uri)){
            new SystemController().logout();
        }else {
            response.getWriter().write("404 Not Found!!");
        }
    }
    ...
}

这样的代码扩展性不太优雅,也不现实,因为我们实际项目中一定不止这几个 Controller,往往是成千上万个 Controller,显然我们不能写成千上万个 if…else…,那么我们如何来改造呢?小伙伴们一定首先就想到了策略模式,来看一下我是怎么优化的

public class DispatcherServlet extends HttpServlet{

    private List<Handler> handlerMapping = new ArrayList<Handler>();

    public void init() throws ServletException {
        try {
            Class<?> memberControllerClass = MemberController.class;

            handlerMapping.add(new Handler()
                .setController(memberControllerClass.newInstance())
                .setMethod(memberControllerClass.getMethod("getMemberById", new Class[]{String.class}))
                .setUrl("/web/getMemberById.json"));
        } catch(Exception e) {
        }
    }

    private void doDispatch(HttpServletRequest request, HttpServletResponse response) {
        //1、获取用户请求的 url
        // 如果按照 J2EE 的标准、每个 url 对对应一个 Serlvet,url 由浏览器输入
        String uri = request.getRequestURI();

        //2、Servlet 拿到 url 以后,要做权衡(要做判断,要做选择)
        // 根据用户请求的 URL,去找到这个 url 对应的某一个 java 类的方法
        //3、通过拿到的 URL 去 handlerMapping(我们把它认为是策略常量)
        Handler handle = null;
        for (Handler h: handlerMapping) {
            if (uri.equals(h.getUrl())) {
                handle = h;
                break;
            }
        }

        //4、将具体的任务分发给 Method(通过反射去调用其对应的方法) Object object = null;
        try {
            object = handle.getMethod().invoke(handle.getController(),request.getParameter("mid"));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        //5、获取到 Method 执行的结果,通过 Response 返回出去
        response.getWriter().write();
    }

    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        try {
            doDispatch(req,resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    class Handler {
        private Object controller;
        private Method method;
        private String url;

        public Object getController() {
            return controller;
        }

        public Handler setController(Object controller) {
            this.controller = controller;
            return this;
        }

        public Method getMethod() {
            return method;
        }

        public Handler setMethod(Method method) {
            this.method = method;
            return this;
        }

        public String getUrl() {
            return url;
        }

        public Handler setUrl(String url) {
            this.url = url;
            return this;
        }
    }
}

上面的代码我结合了策略模式,工厂模式,单例模式.当然我的优化方案不一定是最完美的,仅代表个人观点

猜你喜欢

转载自blog.csdn.net/csharpqiuqiu/article/details/107670713