设计模式--策略模式

96 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第29天,点击查看活动详情

在策略模式中,存在多个策略对象和一个行为随着策略对象改变而改变的 context(上下文) 对象。策略对象的改变会影响context的执行结果。一般来说对于一系列业务如果说他们的业务流程相似,但其中的一些数据处理规则不同,一般都会使用策略模式。而且使用策略模式可以减少大量的if。。else的出现。对于接口而言,使用策略模式可以减少接口暴露,通过运行期间获取不同的service来处理不同的业务场景,使得业务更加存粹。

在生活中其实也是的,我们遇到问题针对不同的问题会制定一系列的解决措施,并且一个人解决问题的习惯或是流程是大致相同的,这也是策略模式的体现。

策略模式

策略模式(Strategy OPattern),属于行为模式。指的是一个类的行为或算法可以在运行期间改变。

涉及角色:

  • 策略对象
  • 策略转发器 (Context)

在策略模式中,策略对象会改变Context运行的结果,也就是Context会随着策略对象的改变而改变。

实现

定义抽象策略对象接口

有各自策略类实现对应逻辑或算法

public interface Strategy {
    /**
     * 抽象方法(处理同一类相似逻辑)
     */
    void doBusiness();
}

实现

public class StrategyImpl1 implements Strategy{
    @Override
    public void doBusiness() {
        System.out.println("调用:"+this.getClass().getName());
    }
}
public class StrategyImpl2 implements Strategy{
    @Override
    public void doBusiness() {
        System.out.println("调用:"+this.getClass().getName());
    }
}

策略转发器Context

根据注入不同的策略对象执行对应不同算法

public class Context {
    /**
     * 将需要转发的策略类组合进来
     */
    private Strategy strategy;

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

    public void doForWard(){
        strategy.doBusiness();
    }
}

测试

public class StrategyTest {
    @Test
    public void test() {
        Context context1 = new Context(new StrategyImpl1());
        context1.doForWard();
        Context context2 = new Context(new StrategyImpl2());
        context2.doForWard();
    }
}

image-20220617002843541.png

说明

策略模式专注提供统一处理入口,统一处理实现同一接口的存在相似算法的类。

image-20220617004133188.png

应用

在开发过程中不饿避免的会遇到一些处理流程相似的的接口,此刻就可以使用策略模式,结合配置可以实现统一处理。

我们以对用户进行增删改查为例

为了方便处理这里的equals只比对name和age。

@Data
@AllArgsConstructor
public class Person {
    private String name;
    private Integer age;
    private Date birthDae;

    @Override
    public boolean equals(Object obj) {
        Person target = (Person) obj;
        return
                this.getAge().equals(target.getAge())
                &&
                this.getName().equals(target.getName());
    }
}

定义增删改查接口的抽象业务接口:

public interface ActionInterface {
    /**
     * 抽象业务
     * @param person
     */
    void doAction(Person person);
}

定义增删改拆接口:

//增
public class AddAction implements ActionInterface{
    @Override
    public void doAction(Person person ) {
        Context.addAction(person);
    }
}
//删
public class DeleteAction implements ActionInterface{
    @Override
    public void doAction(Person person ) {
        Context.deleteAction(person);
    }
}

定义策略转发器:

  • 初始化用户数据PersonList,以及接口数据InterfaceNos
  • 使用静态代码块模拟从数据库加载数据
  • 定义静态方法对PersonList进行增删改查,模拟数据库增删改查

这里我们使用反射来获取具体的策略,实际开发中会将具体策略注入到ioc容器中,并将注入的BeanName持久化的形式保存在数据库中,然后使用SpringContextd对象来获取具体的策略。

public class Context {

    private final static List<Person> personList = new ArrayList<>();
    private final static Map<String, String> interfaceNos = new HashMap<>();

    static {
        //初始化一些数据,模仿数据库查询
        personList.add(new Person("yyc1", 21, new Date(System.currentTimeMillis())));
        personList.add(new Person("yyc2", 22, new Date(System.currentTimeMillis())));

        //接口号数据初始化,接口号,接口名称(这个数据可以配置到数据库内)
        interfaceNos.put("22061601", AddAction.class.getName());
        interfaceNos.put("22061602", DeleteAction.class.getName());
    }

  //利用反射创建实例
    private ActionInterface getInstance(String interfaceNo) throws ClassNotFoundException,
            InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        final String interfaceName = interfaceNos.get(interfaceNo);
        final Class<?> aClass = Class.forName(interfaceName);
        final Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
        return (ActionInterface) declaredConstructor.newInstance();
    }

  //转发策略
    public void doStrategy(String interfaceNo, Person person) throws ClassNotFoundException,
            InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        ActionInterface instance = getInstance(interfaceNo);
        instance.doAction(person);
    }


    /**
     * 对外暴露方法
     */
    public static void deleteAction(Person person) {
        final boolean remove = personList.remove(person);
        System.out.println("======" + "deleteAction" + "======");
        if (remove) {
            System.out.println("success");
        } else {
            System.out.println("faild");
        }
        personList.forEach(System.out::println);
    }

    public static void searchAction(Person person) {
        final Person person1 = personList.get(personList.lastIndexOf(person));
        if (ObjectUtils.isEmpty(person1)) {
            System.out.println("没找到");
        } else {
            System.out.println("找到了:" + person1);
        }
        personList.forEach(System.out::println);
    }
}

测试:

public class DemoTest {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        Context context = new Context();
        Person personAdd = new Person("yyc5", 25, new Date(System.currentTimeMillis()));
        Person personModify = new Person("yyc2", 252123, new Date(System.currentTimeMillis()));
        Person personSearch = new Person("yyc3", 23, new Date(System.currentTimeMillis()));
        context.doStrategy("22061603", personModify);
        context.doStrategy("22061604", personSearch);
    }

  //测试反射
    @Test
    public void test() throws ClassNotFoundException {
        Class<?> aClass = Class.forName("com.roily.designpatterns.dpmain.strategy.strategydemo."+"AddAction");
    }
}

image-20220617015708070.png.

小结

这里我们使用的是反射+多态来创建对象实例的,在大多数使用场景下我们会使用SpringIoc容器去实现,通过ApplicationContext.getBean()方式来获取对象实例。

总结

策略模式属于行为模式,类的具体调用决策权在客户端,所有的实例都是在运行期间生成的。如此实现对于程序来说,如果需要扩展一个接口,只需要增加实现类并配置接口号即可。

缺点就在于过多的策略类的出现。