策略模式(Strategy)从浅到深

version1:策略模式(Strategy)

概念

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

代码实现

目录结构:



具体代码:

Client类调用不同的具体策略,只需要调用同一个concreteStrategy类传入不同的具体策略方法
package controller;

import service.ConcreteStrategy;
import service.Impl.ConcreteStrategyA;
import service.Impl.ConcreteStrategyB;
import service.Impl.ConcreteStrategyC;

/**
 * @author SHshuo
 * @data 2021/10/16--8:41
 * 策略模式
 */
public class Client {
    public static void main(String[] args) {

        ConcreteStrategy concreteStrategy;

//        实例化的时候传入不同的具体策略对象
        concreteStrategy = new ConcreteStrategy(new ConcreteStrategyA());
        concreteStrategy.AlgorithmInterface();

        concreteStrategy = new ConcreteStrategy(new ConcreteStrategyB());
        concreteStrategy.AlgorithmInterface();

        concreteStrategy = new ConcreteStrategy(new ConcreteStrategyC());
        concreteStrategy.AlgorithmInterface();
    }
}
ConcreteStrategy类面向接口(Strategy类)编程、维护一个对Strategy对象的引用
package Context;

/**
 * @author SHshuo
 * @data 2021/10/16--8:42
 * 面向接口编程、体现依赖倒转原则
 * 上下文
 */
public class ConcreteStrategy {
    
    private Strategy strategy;

//    初始化的之后传入具体的策略对象
    public ConcreteStrategy(Strategy strategy){
        this.strategy = strategy;
    }

//    根据具体的策略对象、调用其算法的方法
    public void AlgorithmInterface(){
        strategy.AlgorithmInterface();
    }
}

Strategy接口
package service;

/**
 * @author SHshuo
 * @data 2021/10/16--8:35
 * 如果有类实现不了Strategy,可以使用委托的方式添加AlgorithmInterface方法到Client类中
 */
public interface Strategy {

    void AlgorithmInterface();
}
具体实现Strategy接口的策略方法、ConcreteStrategyA类、ConcreteStrategyB类、ConcreteStrategyC类大体一致
package service.Impl;

import service.Strategy;

/**
 * @author SHshuo
 * @data 2021/10/16--8:36
 */
public class ConcreteStrategyA implements Strategy {

    @Override
    public void AlgorithmInterface() {
        System.out.println("A算法实现具体内容");
    }
}

UML类图

  • 三个具体的实现类(ConcreteStrategyA)实现接口(Strategy);
  • Context维护Strategy对象的引用,构造函数传入具体的实现类,并赋给成员变量,成员变量调用具体的函数;
  • Client调用Context类,传入具体的实现类。


好处

  • 解决客户端大量if else的分支(将这些分支的行为封装在一个个独立的Strategy类中,可以在使用这些行为类来消除条件语句);
  • 简化了单元测试(每个算法都有自己的类,可以通过自己的接口单独测试);

问题

客户端去判断具体的实现类,增加了客户端的压力,没有充分与具体的实现类进行解耦;


Version2:引入工厂模式

针对客户端的改进,将改动部分移动到Context,使得客户端的代码不需要改动;

  • 使用工厂模式,将客户端的代码判断具体的实现类移动到Context;
  • 客户端传入type,对应具体的实现类;
  • Context的构造函数,将传入的type进行分支的判断,初始化不同的具体实现类。

改进代码:

Client(不需要改动)
package controller;

import Context.ConcreteStrategy;
import service.Impl.ConcreteStrategyA;
import service.Impl.ConcreteStrategyB;
import service.Impl.ConcreteStrategyC;

/**
 * @author SHshuo
 * @data 2021/10/16--8:41
 * 策略模式与工厂模式结合
 */
public class Client {
    public static void main(String[] args) {

        ConcreteStrategy concreteStrategy;

        /**
         * 客户端传入不同的参数、例如传入的是下拉框的选项,这里写一个就够了,也就是说客户端代码是不变的;
         * 减轻客户端的职责交给Context,实现了具体的策略类与客户端解耦。
         *
         * 单纯的策略模式,这里仍需要自己定义具体实现的策略,每次仍需要添加分支,修改客户端的代码,增加客户端的压力
         */
        concreteStrategy = new ConcreteStrategy("ConcreteStrategyA");
        concreteStrategy.AlgorithmInterface();

//        concreteStrategy = new ConcreteStrategy("ConcreteStrategyB");
//        concreteStrategy.AlgorithmInterface();
//
//        concreteStrategy = new ConcreteStrategy("ConcreteStrategyC");
//        concreteStrategy.AlgorithmInterface();
    }
}
Context
package Context;

import service.Impl.ConcreteStrategyA;
import service.Impl.ConcreteStrategyB;
import service.Impl.ConcreteStrategyC;
import service.Strategy;

/**
 * @author SHshuo
 * @data 2021/10/16--8:42
 * 简单工厂模式
 * 由Context作为工厂,创建具体的实现类
 */
public class ConcreteStrategy {

    private Strategy strategy;


    /**
     * 利用工厂模式在构造函数中根据不同分支创建具体的对象,
     * 而不需要从客户端传入具体的对象,实现了客户端与具体的实现类的解耦
     * @param type
     */
    public ConcreteStrategy(String type){
        switch (type){
            case"ConcreteStrategyA":
                strategy = new ConcreteStrategyA();
                break;
            case"ConcreteStrategyB":
                strategy = new ConcreteStrategyB();
                break;
            case"ConcreteStrategyC":
                strategy = new ConcreteStrategyC();
                break;
        }
    }

//    根据具体的策略对象、调用其算法的方法
    public void AlgorithmInterface(){
        strategy.AlgorithmInterface();
    }
}

好处

  • 增加具体实现类的时候,由于不需要传入具体的实现类,所以客户端代码不用改变;
  • 同时充分与具体实现类解耦,减轻了客户端的职责,交给Context的工厂完成创建对应具体实现类的对象。

问题

  • 增加具体实现类的时候,仍需要改动Context的代码,需要修改分支增加对应分支的行为。


version3:引入反射

针对Context类的改进,利用反射;使得Context类的代码不需要改动;
  • 利用反射解决Context类的分支问题
  • 这样增加一个具体的实现类的时候,只需要实现对应的接口即可;客户端、Context类的代码都不需要改变

改进代码:

Context

package Context;

import service.Strategy;

import java.lang.reflect.Constructor;

/**
 * @author SHshuo
 * @data 2021/10/16--8:42
 * 使用反射实例化具体的类
 * 不需要改动Context的代码
 */
public class ConcreteStrategy {

    private Strategy strategy;


    /**
     * 利用反射根据类名,创建对应具体类的实例,
     * 不需要Switch,case分支进行传入判断,而不需要改动Context类的代码
     * @param type
     */
    public ConcreteStrategy(String type){
        try {
            Class c = Class.forName("service.Impl." + type);
            Constructor constructor = c.getConstructor();
            strategy = (Strategy) constructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

//    根据具体的策略对象、调用其算法的方法
    public void AlgorithmInterface(){
        strategy.AlgorithmInterface();
    }
}

好处

  • 增加具体实现类的时候,只需要实现对应的接口即可;客户端、Context类的代码都不需要改变;

补充springboot获取bean的方式

通过实现ApplicationContextAware接口,创建ApplicationContext对象,调用里面的getBean方法获取到bean。
ApplicationContext app = new ApplicationContext();
Strategy strategy = (Strategy)app.getBean("类名");


应用:

策略模式感觉类似于controller层注入数据的时候,都是注入使用的接口,但是对于多个类实现一个接口的时候,需要在注入的时候指明哪个类
@Autowired + @Qualifier("hshuo")
相当于@Resource(name="hshuo")

参考:

  • 大话设计模式这本书,写的很幽默;

源代码:



hshuo的面试之路 文章被收录于专栏

作者目标是找到一份Java后端方向的工作 此专栏用来记录从Bilibili、书本、其他优质博客上面学习的内容 用于巩固、总结内容 主要包含Docker、Dubbo、Java基础、JUC、Maven、MySQL、Redis、SpringBoot、SpringCloud、数据结构、杂文、算法、计算机网络、操作系统、设计模式等相关内容

全部评论

相关推荐

牛客717484937号:双飞硕没实习挺要命的
点赞 评论 收藏
分享
我也曾抱有希望:说的好直白
点赞 评论 收藏
分享
2 1 评论
分享
牛客网
牛客企业服务