JAVA:策略模式的实战使用

1、前言

什么是策略模式?

在处理一个东西时,如果有多种不同的处理方式,这是我们可以定义一个接口类,用于规范处理的步骤,而具体的处理方法交由实现类去解决,这就是策略模式。

在使用策略模式后,只需要将父类引用指向具体的子类对象即可,可以避免大量代码的重复实现。

2、策略模式的简单示例

打个比方:现在要将土豆做成土豆泥

先定义接口类,仅需定义在单一步骤中处理方式可不同的步骤

java 复制代码
public interface MakeTuDou {
    //切小
    void cut();
    //变熟
    void cooked();
    //变成土豆泥
    void ni();
}

定义一个土豆泥的简单做法

java 复制代码
public class TuDouToolEasy implements MakeTuDou{
    @Override
    public void cut() {
        System.out.println("用刀将土豆切成小块");
    }
    @Override
    public void cooked() {
        System.out.println("将土豆蒸熟");
    }
    @Override
    public void ni() {
        System.out.println("将土豆捣成土豆泥");
    }
}

定义一个土豆泥的进阶做法

java 复制代码
public class TuDouToolDifficulty implements MakeTuDou{
    @Override
    public void cut() {
        System.out.println("用削皮刀将土豆削皮");
        System.out.println("用刀将土豆切成片后,再切成条");
    }
    @Override
    public void cooked() {
        System.out.println("将土豆蒸熟");
    }
    @Override
    public void ni() {
        System.out.println("用破壁机将土豆打成土豆泥");
    }
}

这样仅需切换定义的子类,就可以实现土豆泥做法的切换,可以避免大量if else结构,让代码更简单易懂。

java 复制代码
public class Demo {
    public static void main(String[] args) {
        TuDouToolEasy td = new TuDouToolEasy();
//        TuDouToolEasy td = new TuDouToolDifficulty();
        System.out.println("清洗土豆,洗去泥土");
        td.cut();
        System.out.println("将切小的土豆放在水中侵泡一会儿");
        td.cooked();
        System.out.println("将土豆放凉");
        td.ni();
        System.out.println("加入调味料");
    }
}

运行结果:

3、策略模式的进阶实战

在上面的示例中,为大家展示了策略模式的简单用法,帮助大家对策略模式有了一个初步的了解。

相信大家也可以看出,在上述的示例中有一个极为明显的问题,就是在代码中很生硬的直接创建了对象实例。

一个两个还好,如果更多就会出现大量的if else或switch,以极其生硬的方式呈现出来,每多一种实现方式都会在上面增加一层,这是不被允许的。

接下来,我会将spring的ioc与策略模式相结合,教给大家一种可用于实际开发的策略模式。

3.1 定义注解类

假设A是被Component标记的注解,那么所有被A标记的类都会被收集到spring的ioc容器中。

注解中的value属性,就是区分不同实现类的依据。

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface DataInfo {
    String value() default "";
}

3.2 定义接口类

这里为做某一项事,定义步骤

java 复制代码
public interface DataProcess {
    String toClean(String s);
    void toShow(String s);
}

3.3 定义实现类

这里的实现类必须被DataInfo注解标记,好被注册到ioc容器中。

java 复制代码
@DataInfo("1")
public class MakeBig implements DataProcess {
    @Override
    public String toClean(String s) {
        s += "big";
        return s;
    }
    @Override
    public void toShow(String s) {
        System.out.println("我是big:" + s);
    }
}
java 复制代码
@DataInfo("2")
public class MakeSmall implements DataProcess {
    @Override
    public String toClean(String s) {
        s += "small";
        return s;
    }
    @Override
    public void toShow(String s) {
        System.out.println("我是small:" + s);
    }
}

3.4 定义收集类

在实现类被注册到ioc后,我们可以将不同接口的实现类收集到不同的区域中。

但因为在实际开发中通常会有多个场景需要使用到策略模式,为了避免每用一次策略模式就重写一个收集类,所以采用了一个收集父类,多个收集子类的模式。

收集父类:负责收集实现类的代码实现。

收集子类,负责启动收集这一过程,收集哪些实现类,获取区分依据。

3.4.1 收集父类

java 复制代码
@Data
public abstract class DemoRegister <k,v extends Annotation>{
//    这个是接口类
    private Class<k> dataInfoClass;
//    这个是标注的注解,用于收集和区分实现类
    private Class<v> annotation;
//    这是ioc容器,需要从这里获取实现类
    private ApplicationContext applicationContext;
//    采用map存放实现类,是因为在获取实现类时,要有区分依据
    private Map<String,k> map = new HashMap<>();
    public DemoRegister(Class<k> dataInfoClass,Class<v> annotation,ApplicationContext applicationContext){
        this.dataInfoClass = dataInfoClass ;
        this.annotation = annotation ;
        this.applicationContext = applicationContext ;
        init();
    }
    private void init(){
//        根据注解从容器中获取实现类
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(annotation);
        beansWithAnnotation.values().stream().forEach(
                s-> {
//                    获取实现类上的注解
                    v annotationData = AnnotationUtils.findAnnotation(s.getClass(), annotation);
//                    获取区分字段
                    String key = getKey(annotationData);
//                    根据注解上的区分字段,来存放实现类对象
                    putData(key,(k)s);
                }
        );
    }
    public String getKey(v annotation){
//        因为这里采用了泛型,所以获取不到注解上的属性,这里是交给子类去获取
        throw new RuntimeException("本方法没有被实现");

    }
    public void putData(String key,k value){
        this.map.put(key,value);
    }
    public k getData(String key){
        k o = this.map.get(key);
        return o;
    }
}

3.4.2 收集子类

java 复制代码
@Component
public class DataInfoRegister extends DemoRegister<DataProcess, DataInfo> {
//    这里因为只有一个构造器,所以spring会自动传入applicationContext
    public DataInfoRegister(ApplicationContext applicationContext) {
        super(DataProcess.class, DataInfo.class,applicationContext);
    }
    @Override
    public String getKey(DataInfo dataInfo){
//        这里由子类自行处理,返回区分依据
        return dataInfo.value();
    }

}

3.5 运行演示

java 复制代码
@SpringBootTest
class DataInfoRegisterTest {
    @Autowired
    public DataInfoRegister dataInfoRegister;
    @Test
    public void test(){
        BeansManagement.getApplicationContext();

        DataProcess data = dataInfoRegister.getData("1");
        String clean = data.toClean("1");
        data.toShow(clean);

        DataProcess data1 = dataInfoRegister.getData("2");
        String clean1 = data1.toClean("2");
        data1.toShow(clean1);
    }
}

运行结果:

4、结语

在策略模式的进阶用法上,有几点要注意:

  1. 注解类、接口类、收集子类和实现类之间的对应关系是一对一对一对多。
  2. 注解类是区分实现类的重要依据,所以注解类上的区分字段的值一定是唯一的。
  3. 收集父类可以有多个子类,每个子类都代表着一个策略模式的使用。
  4. 注解类的属性值是在收集子类中被获取的。
相关推荐
星沉远浦26 分钟前
用Gemini高效解决Java代码报错难以定位的问题
java
用户298698530144 小时前
Word 文档字符级格式化:Java 实现方案详解
java·后端
笨鸟飞不快4 小时前
从单个服务到集群:一次完整的性能排查复盘
java·前端
荣码5 小时前
用Streamlit给AI应用套个界面,10行代码出Web页面
java·python
SamDeepThinking5 小时前
Java微服务练习方式
java·后端·微服务
朦胧之15 小时前
AI 编程-老项目改造篇
java·前端·后端
程序猿大帅20 小时前
别再只当调包侠了:用 Spring AI 落地 Function Calling,我被大模型硬生生砸出了三个大坑
java
程序员晓琪21 小时前
约定大于配置:基于 Java 包名自动生成 API 版本路由的最佳实践
java·spring boot·后端
Flittly21 小时前
【AgentScope Java新手村系列】(11)中断与恢复
java·spring boot·spring
众少成多积小致巨21 小时前
JNI (Java Native Interface) 技术手册中文参考指南
android·java·c++