设计模式之策略模式

模式定义

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。

策略模式是一种对象行为型模式。

模式结构

策略模式包含如下角色:

  • Context: 环境类
  • Strategy: 抽象策略类
  • ConcreteStrategy: 具体策略类

时序图

代码分析

举个例子,用户支付时选择不同银行卡,不同渠道商品可能有不同折扣,可以使用策略模式设计这样的一个接口。

首先定义一个接口,接口有一个方法,参数为渠道 ID 和商品 ID

package top.waterlaw.pay;

import java.math.BigDecimal;

public interface Stratygy {

    BigDecimal calRecharge(Integer channelId, Integer goodsId);
}

再来一个实现类, 比如 ICBC 工行卡支付,实现这个接口

package top.waterlaw.pay.impl;

import top.waterlaw.pay.Pay;
import top.waterlaw.pay.Stratygy;
import java.math.BigDecimal;
import java.util.Map;

@Pay(1)
public class ICBCPay implements Stratygy {
    // Spring 框架需注入 Bean
    private Map channelMaper;
    private Map goodsMaper;


    public BigDecimal calRecharge(Integer channelId, Integer goodsId) {
        //- 调用数据库
        // getChannel(channelId) getGoodDisCount(goodsId)
        return null;
    }
}

接下来我们创建一个上下文环境,根据 渠道 ID 和商品 ID 去调用不同接口实现类

package top.waterlaw.pay;

import java.math.BigDecimal;

public class Context {
    private Stratygy stratygy;

    public BigDecimal calRecharge(Integer channelId, Integer goodsId) throws Exception {
        StrategryFactory factory = StrategryFactory.getInstance();
        Stratygy stratygy = factory.create(channelId);
        return stratygy.calRecharge(channelId, goodsId);
    }

    //- 支付接口---->类型---->ICBC,ABNK

    //- 如果使用 Spring 需要再配置 BeanUtil
}

这里用到了工厂类,策略模式要求我们把所有的实现类放到一个 map 中,HashMap, 键为 channelId, value 则是具体实现类类名, 如 “top.waterlaw.pay.impl.ICBCPay”,我们还需一个单例工厂管理所有的实现类。

package top.waterlaw.pay;

import org.reflections.Reflections;

import java.util.HashMap;
import java.util.Set;

public class StrategryFactory {
    //- 饿汉模式
    private static StrategryFactory strategryFactory = new StrategryFactory();
    // String "XXX.class"
    public static HashMap<Integer, String> source_map = new HashMap<Integer, String>();

    static {
        // 反射扫描出包下所有的类
        Reflections reflections = new Reflections("top.waterlaw.pay.impl");
        // 取出带有 Pay 注解的类
        Set<Class<?>> classList = reflections.getTypesAnnotatedWith(Pay.class);
        for (Class clazz: classList) {
            Pay t = (Pay) clazz.getAnnotation(Pay.class);
            source_map.put(t.value(), clazz.getCanonicalName());
        }
    }

    private StrategryFactory() {}

    public Stratygy create(int type) throws Exception {
        String clazz = source_map.get(type);
        Class<?> clazz_ = Class.forName(clazz);
        return (Stratygy)clazz_.newInstance();
    }

    public static StrategryFactory getInstance() {
        return strategryFactory;
    }
}

使用@Pay 注解和 Java 反射机制, 在 static 代码块完成所有实现类的扫描并添加到 map 中,可以使用如下反射包

        <dependency>
            <groupId>org.reflections</groupId>
            <artifactId>reflections</artifactId>
            <version>${reflections-version}</version>
        </dependency>

@Pay 注解的定义如下

package top.waterlaw.pay;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pay {
    int value();
}

只要在实现类中使用注解 @Pay(1) 就可以很方便地将实现类加入策略模式中,增加了一种支付方式也无需修改原有代码。

优点

策略模式的优点

  • 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  • 策略模式提供了管理相关的算法族的办法。
  • 策略模式提供了可以替换继承关系的办法。
  • 使用策略模式可以避免使用多重条件转移语句。

缺点

策略模式的缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

适用环境

在以下情况下可以使用策略模式:

  • 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  • 一个系统需要动态地在几种算法中选择一种。
  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。

参考

发表评论

评论内容
 

评论列表, 共 0 条评论