设计模式之策略模式与责任链模式详解和应用

Posted 赵广陆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之策略模式与责任链模式详解和应用相关的知识,希望对你有一定的参考价值。

目录


1.策略模式

1.1 目标

1、 掌握策略模式和责任链模式的应用场景;

2、 通过学习策略模式来消除程序中大量的if…else…和 switch语 句 ;

3、 掌握策略模式和委派模式的结合使用;

4、 深刻理解责任链模式和建造者模式的结合应用

1.2.内容定位

1 、已经掌握建造者模式和委质模式的人群。

2、希望通过对策略模式的学习,来消除程序中大量的冗余代码和多重条件转移语句的人群。

3、希望通过学习责任链模式宜构校验逻辑的人群。

1.3.定义

策略模式 (Strategy Pattern) 又叫也叫政策摆式 (Policy Pattern) , 它是将定义的算法家族、分 别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。属千行为型模式。

原 文: Define a family of algorithms,encapsulate each one,and make them interchangeable.

策略摆式使用的就是面向对象的继承和多态机制,从而实现同—行为在不同场萦下具备不同实现。

1.4.应用场景

策略模式在生活场景中应用也非常多。比如—个入的交税比率与他的工资有关,不同的工资水平对 应不同的税率。再比如我们在互联网移动支付的大背景下,每次下单后付款前,需要选择支付方式。

策略模式可以解决在有多种算法相似的情况下,使用 if…else 或 switch… case 所带来的复杂性和臃肿性。在日常业务开发中,策略模式适用于以下场景 :

1 、针对同—类型问题,有多种处理方式,每—种都能独立解决问题 ;

2、 算法需要自由切换的场果 ;

3、需要屏蔽算法规则的场果。

首先来看下策略模式的通用 UML 类图 :

从 UML 类图中,我们可以看到,策略摆式主要包含三种角色 :

上下文角色 (Context) : 用来操作策略的上下文环境,屏蔽高层模块(客户端)对策略, 算法的 直接访问,封装可能存在的变化 ;

抽象策略角色 (Strategy) : 规定策略或算法的行为 ;

具体策略角色 (ConcreteStrategy) : 具体的策略或算法实现。

注意: 策略模式中的上下文环境 (Context) , 其职责本来是隔离客户端与策略类的耦合, 让客户端完全与上下文环境 沟通, 无需关系具体策略。

1.5.促销优惠业务场景

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

首先我们创建—个促销策略的抽象 PromotionStrategy :

 /**
  * 促销策略抽象 
  */
 public interface IPromotionStrategy 
     void doPromotion();
 

然后分别创建优惠券抵扣策略 CouponStrategy 类、返现促销策略 CashbackStrategy 类、拼团优惠策略 GroupbuyStrategy 类和无优惠策略 EmptyStrategy 类 :

 public class CouponStrategy implements IPromotionStrategy 
     public void doPromotion() 
         System.out.println("使用优惠券抵扣");
     
 

CashbackStrategy 类 :

 public class CashbackStrategy implements IPromotionStrategy 
     public void doPromotion() 
         System.out.println("返现,直接打款到支付宝账号");
     
 

GroupbuyStrategy 类 :

 public class GroupbuyStrategy implements IPromotionStrategy 
     public void doPromotion() 
         System.out.println("5人成团,可以优惠");
     
 

EmptyStrategy 类 :

 public class EmptyStrategy implements IPromotionStrategy 
     public void doPromotion() 
         System.out.println("无优惠");
     
 

然后创建促销活动方案 PromotionActivity 类 :

 public class PromotionActivity 
     private IPromotionStrategy strategy;
 
     public PromotionActivity(IPromotionStrategy strategy) 
         this.strategy = strategy;
     
 
     public void execute()
         strategy.doPromotion();
     
 

编写客户端测试类 :

 public class Test 
     public static void main(String[] args) 
         PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
         PromotionActivity activityllll = new PromotionActivity(new CashbackStrategy());
         activity618.execute();
         activityllll.execute();
     
 

运行效果如下:

 使用优惠券抵扣
 返现,直接打款到支付宝账号

此时,小伙伴们会发现,如果把上面这段则试代码放到实际的业务场景其实并不实用。因为我们做活动时候往往是要根据不同的需求对促销策略进行动态选择的,并不会一次性执行多种优惠。所以,我 们的代码通常会这样写 :

 public class Test 
     public static void main(String[] args) 
         PromotionActivity promotionActivity = null;
         String promotionKey = "COUPON";
         if (StringUtils.equals(promotionKey, "COUPON")) 
             promotionActivity = new PromotionActivity(new CouponStrategy());
          else if (StringUtils.equals(promotionKey, "CASHBACK")) 
             promotionActivity = new PromotionActivity(new CashbackStrategy());
         
         promotionActivity.execute();
     
 

这样改造之后,满足了业务需求,客户可根据自己的需求选择不同的优惠策略了。但是,经过—段 时间的业务积累,我们的促销活动会越来越多。千是,我们的程序猿小哥哥就忙不来了,每次上活动之前都要通宵改代码,而且要做重复测试,判断逻辑可能也变得越来越复杂。这时候,我们是不需要思考 代码是不是应该重构了?回顾我们之前学过的设计模式应该如何来优化这段代码呢?其实,我们可以结 合单例模式和工厂摆式。创建 PromotionStrategyFactory 类 :

 public class PromotionStrategyFacory 
 
     private static final IPromotionStrategy EMPTY = new EmptyStrategy();
     private static Map<String, IPromotionStrategy> PROMOTIONS = new HashMap<String, IPromotionStrategy>();
 
     static 
         PROMOTIONS.put(PromotionKey.COUPON, new CouponStrategy());
         PROMOTIONS.put(PromotionKey.CASHBACK, new CashbackStrategy());
         PROMOTIONS.put(PromotionKey.GROUPBUY, new GroupbuyStrategy());
     
 
     private PromotionStrategyFacory() 
     
 
     public static IPromotionStrategy getPromotionStrategy(String promotionKey) 
         IPromotionStrategy strategy = PROMOTIONS.get(promotionKey);
         return strategy == null ? EMPTY : strategy;
     
 
     public static Set<String> getPromotionKeys() 
         return PROMOTIONS.keySet();
     
 
     private interface PromotionKey 
         String COUPON = "COUPON";
         String CASHBACK = "CASHBACK";
         String GROUPBUY = "GROUPBUY";
     
 

这时候我们客户端代码就应该这样写了 :

 public class Test 
     public static void main(String[] args) 
         PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
         PromotionActivity activityllll = new PromotionActivity(new CashbackStrategy());
         activity618.execute();
         activityllll.execute();
     
 

运行效果如下:

 使用优惠券抵扣

代码优化之后 , 是不是我们程序猿小哥哥的维护工作就轻松了?每次上新活动,不影响原来的代码 逻辑。

1.6 用策略模式实现选择支付方式的业务场景

为了加深对策略模式的理解,我们再来举一个案例。 相信小伙伴们都用过支付宝、微信支付、银联 支付以及京东白条。 —个常见的应用场景就是大家在下单支付时会提示选择支付方式,如果用户未选, 系统也会默认好推荐的支付方式进行结算。来看一下类图,下面我们用策略模式来描拟此业务场罢 :

创建 Payment 抽象类,定义支付规范和支付逻辑,代码如下 :

 public abstract class Payment 
 
     public abstract String getName();
 
     //通用逻辑放到抽象类里面实现
     public MsgResult pay(String uid, double amount) 
         //余额是否足够
         if (queryBalance(uid) < amount) 
             return new MsgResult(500, "支付失败", "余额不足");
         
         return new MsgResult(200, "支付成功", "支付金额" + amount);
     
 
     protected abstract double queryBalance(String uid);
 

分别创建具体的支付方式,支付宝 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 263;
     
 

银联支付 UnionPay 类 :

 public class UnionPay extends Payment 
     public String getName() 
         return "银联支付";
     
 
     protected double queryBalance(String uid) 
         return 120;
     
 

创建支付状态的包装类 MsgResult:

 public class MsgResult 
     private int code;
     private Object data;
     private String msg;
 
     public MsgResult(int code, String msg, Object data) 
         this.code = code;
         this.data = data;
         this.msg = msg;
     
 
     @Override
     public String toString() 
         return "MsgResult" +
                 "code=" + code +
                 ", data=" + data +
                 ", msg='" + msg + '\\'' +
                 '';
     
 

创建支付策略管理类 :

 public class PayStrategy 
     public static  final String ALI_PAY = "AliPay";
     public static  final String JD_PAY = "JdPay";
     public static  final String WECHAT_PAY = "WechatPay";
     public static  final String UNION_PAY = "UnionPay";
     public static  final String DEFAULT_PAY = ALI_PAY;
 
     private static Map<String,Payment> strategy = new HashMap<String,Payment>();
 
     static 
         strategy.put(ALI_PAY,new AliPay());
         strategy.put(JD_PAY,new JDPay());
         strategy.put(WECHAT_PAY,new WechatPay());
         strategy.put(UNION_PAY,new UnionPay());
     
 
     public static Payment get(String payKey)
         if(!strategy.containsKey(payKey))
             return strategy.get(DEFAULT_PAY);
         
         return strategy.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;
     
 
     public MsgResult pay()
         return pay(PayStrategy.DEFAULT_PAY);
     
 
     public MsgResult pay(String payKey)
         Payment payment = PayStrategy.get(payKey);
         System.out.println("欢迎使用" + payment.getName());
         System.out.println("本次交易金额为" + amount + ",开始扣款");
         return payment.pay(uid,amount);
     
 

测试代码 :

 public class Test 
     public static void main(String[] args) 
         Order order = new Order("1","2020031401000323",324.5);
         System.out.println(order.pay(PayStrategy.UNION_PAY));
     
 

运行结果 :

 欢迎使用银联支付
 本次交易金额为324.5,开始扣款
 MsgResultcode=500, data=余额不足, msg='支付失败'

希望通过大家耳熟能详的业务场景来举例,让小伙伴们更深刻地理解策略模式。希望小伙伴们在面 试和工作体现出自己的优势。

1.7 策略模式在框架源码中的体现

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

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

Comparator抽象下面有非常多的实现类,我们经常会把 Comparator作为参数传入作为排序策略, 例如 Arrays 类的 parallelSort 方法等 :

 public class Arrays 
     public static void parallelSort(byte[] a) 
         int n = a.length, p, g;
         if (n <= MIN_ARRAY_SORT_GRAN ||
             (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
             DualPivotQuicksort.sort(a, 0, n - 1);
         else
             new ArraysParallelSortHelpers.FJByte.Sorter
             (null, a, new byte[n], 0, n, 0,
              ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
              MIN_ARRAY_SORT_GRAN : g).invoke();
     
 

还有 TreeMap 的构造方法:

 public class TreeMap<K,V>
     extends AbstractMap<K,V>
     implements NavigableMap<K,V>, Cloneable, java.io.Serializable
 
     public TreeMap(Comparator<? super K> comparator) 
         this.comparator = comparator;
     
 

这就是 Comparator 在JDK原码中的应用。那我们来看策略模式在 Spring源码中的应用,来看 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以上是关于设计模式之策略模式与责任链模式详解和应用的主要内容,如果未能解决你的问题,请参考以下文章

前端设计模式之责任链模式

工厂模式策略者模式责任链模式综合应用

GoF 23 种设计模式之策略模式和责任链模式

GoF 23 种设计模式之策略模式和责任链模式

设计模式之责任链模式

行为型设计模式之责任链模式