设计模式-策略模式

一、什么是策略模式?

策略模式(Strategy Pattern)也叫政策模式(Policy Pattern),它是将定义的算法家族分别封装起来,让它们之间可以相互替换,从而让算法的变化不影响到使用算法的用户。属于行为型模式。可以避免多重if...else...和switch语句。

对于策略的理解

  • 比如我们网购可以选择不同的支付方式:支付宝支付、微信支付、银联支付等等,这些支付方式,每一种都是一种策略。

  • 再比如我们去逛商场,商场现在正在搞活动,有打折的、有满减的、有返利的等等,其实不管商场如何进行促销,说到底都是一些算法,这些算法本身只是一种策略,并且这些算法是随时都可能互相替换的,比如针对同一件商品,今天打八折、明天满100减30,这些策略间是可以互换的。

策略模式把对象本身和运算规则区分开来,因此我们整个模式也分为三个部分。

  • 上下文(Context):用来操作策略的上下文环境。上下文是依赖于策略接口的类,即上下文包含有策略声明的变量。上下文中提供了一个方法,该方法委托策略变量调用具体策略所实现的策略接口中的方法。

  • 抽象策略类(Strategy):策略的抽象,策略是一个接口,该接口定义算法标识。

  • 具体策略类(ConcreteStrategy):具体的策略实现,继承自Strategy。具体策略实现策略接口所定义的抽象方法,即给出算法标识的具体算法。

1.Context上下文

​ Context上下文角色,也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。

/**
 * 上下文类:
 */
public class Context {
    Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    //上下文接口
    public void contextInterface() {
        strategy.algorithmInterface();
    }
}

2. 策略角色

​ 抽象策略角色,是对策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。

/**
 * 策略类
 */
public interface Strategy {
    //算法方法
    public  void algorithmInterface();
}

3.具体策略角色

​ 用于实现抽象策略中的操作,即实现具体的算法。

/**
 * 具体策略类A
 */
public class ConcreteStrategyA implements Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("算法A实现");
    }
}

/**
 * 具体策略类B
 */
public class ConcreteStrategyB implements Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("算法B实现");
    }
}

二、一个具体例子

支付方式选择

策略类

/**
 * 策略类:
 * 抽取出支付方法
 */
public interface Payment{
    public  void pay();
}

策略实现类

/**
 * 策略实现类
 */
public class AliPay implements Payment {
    @Override
    public void pay() {
        System.out.println("使用支付宝支付");
    }
}
/**
 * 策略实现类
 */
public class WeChatPay implements Payment {
    @Override
    public void pay() {
        System.out.println("使用微信支付");
    }
}
/**
 * 策略实现类
 */
public class UnionPay implements Payment {
    @Override
    public void pay() {
        System.out.println("使用银联支付");
    }
}

支付方式工厂:模拟从数据库获取所有支付方式

/**
 * 模拟从数据库获取所有支付方式,并保存到Map
 */
public class PaymentFactory {
    //保存支付方式
    private static Map<String,Payment> allPays = new HashMap<String,Payment>();
    private PaymentFactory(){}

   static {
       allPays.put("ALIPAY",new AliPay());
       allPays.put("WECHAT",new WeChatPay());
       allPays.put("UNION",new UnionPay());
   }

   public static Map getAllPays(){
        return allPays;
   }
}

Context上下文

/**
 * 上下文Context
 * 订单类
 */
public class Order {
    private String orderId;//订单号
    private BigDecimal amount;//金额
    private Payment payment;//支付方式

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

    public void doPay(){
        this.payment.pay();//调用策略实现类的方法
        System.out.println("支付金额为:" + amount);
    }
    //setter
    public void setPayment(Payment payment) {
        this.payment = payment;
    }
}

测试类

public class AppTest {
    public static void main(String[] args) {
        //创建订单
        Order order = new Order("D123456",new BigDecimal(100));
        //客户选择支付方式,通常界面是一组单选框
        //这里假设客户选择的是支付宝支付
        Payment payment = (Payment) PaymentFactory.getAllPays().get("ALIPAY");
        order.setPayment(payment);
        //开始支付
        order.doPay();
    }
}

测试结果

使用支付宝支付
支付金额为:100

三、策略模式的优点

  • 上下文和具体策略是松耦合关系。因此上下文只知道它要使用某一个实现Strategy接口类的实例,但不需要知道具体是哪一个类。

策略模式满足“开-闭原则”。

  • 当增加新的具体策略时,增加一个策略实现接口即可。不需要修改上下文类的代码,上下文就可以引用新的具体策略的实例。
  • 算法可以自由切换。
  • 避免使用多重条件判断(如果不用策略模式我们可能会使用多重条件语句,不利于维护)

四、策略模式的缺点

  • 策略类数量会增多,每个策略都是一个类,复用的可能性很小
  • 所有的策略类都需要对外暴露:客户端必须了解所有的策略,清楚它们的不同:如果由客户端来决定使用何种算法,那客户端必须知道所有的策略,清楚各个策略的功能和不同,这样才能做出正确的选择,但是这暴露了策略的具体实现。
  • 只适合偏平的算法结构:由于策略模式的各个策略实现是平等的关系(可相互替换),实际上就构成了一个扁平的算法结构。即一个策略接口下面有多个平等的策略实现(多个策略实现是兄弟关系),并且运行时只能有一个算法被使用。这就限制了算法的使用层级,且不能被嵌套。

五、使用场景

  • 多个类只有算法或行为上稍有不同的场景
  • 算法需要自由切换的场景
  • 需要屏蔽算法规则的场景

六、注意事项

  • 如果一个系统的策略多于四个,就需要考虑使用混合模式来解决策略类膨胀的问题

七、策略模式的本质

  分离算法,选择实现。

  如果你仔细思考策略模式的结构和功能的话,就会发现:如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程,就能够享受到面向接口编程带来的好处,通过一个统一的策略接口来封装和分离各个具体的策略实现,无需关系具体的策略实现。

  貌似没有上下文什么事,但是如果没有上下文的话,客户端就必须直接和具体的策略实现进行交互了,尤其是需要提供一些公共功能或者是存储一些状态的时候,会大大增加客户端使用的难度;引入上下文之后,这部分工作可以由上下文来完成,客户端只需要和上下文进行交互就可以了。这样可以让策略模式更具有整体性,客户端也更加的简单。

  策略模式体现了开闭原则:策略模式把一系列的可变算法进行封装,从而定义了良好的程序结构,在出现新的算法的时候,可以很容易的将新的算法实现加入到已有的系统中,而已有的实现不需要修改。

  策略模式体现了里氏替换原则:策略模式是一个扁平的结构,各个策略实现都是兄弟关系,实现了同一个接口或者继承了同一个抽象类。这样只要使用策略的客户端保持面向抽象编程,就可以动态的切换不同的策略实现以进行替换。

八、策略模式在JDK源码中应用

  • 比较器java.util.Comparator接口(抽象的策略)

     int compare(T o1, T o2);//比较o1和o2
    

    一个类实现该接口,并实现里面的compare方法,该类成为具体策略类(可由开发人员自己编写)。

    Collections类就是环境角色,他将集合的比较封装成静态方法对外提供api。

    比如下方java.util.Collections类中的max方法,获取集合中的最大值。

/**
*   jdk源码
*/
public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) {
        if (comp==null)
            return (T)max((Collection) coll);

        Iterator<? extends T> i = coll.iterator();
        T candidate = i.next();
		//循环取出集合中的元素并比较,将比较结果中最大值赋值给candidate
        while (i.hasNext()) {
            T next = i.next();
            if (comp.compare(next, candidate) > 0)
                candidate = next;
        }
        return candidate;
 }
posted on 2021-12-16 16:32  houmoney  阅读(63)  评论(0)    收藏  举报