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. 注解类的属性值是在收集子类中被获取的。
相关推荐
码界筑梦坊1 小时前
282-基于Python的豆瓣音乐可视化分析推荐系统
开发语言·python·信息可视化·数据分析·flask·vue
LJianK11 小时前
java多态
java·开发语言·python
_Evan_Yao1 小时前
栈与队列:后进先出与先进先出的智慧
开发语言·python
z落落1 小时前
C# 构造函数(无参/有参/重载/this)+析构函数(终结器)|GC 垃圾回收
java·开发语言·c#
武子康1 小时前
Java-12 深入浅出 MyBatis 二级缓存详解:跨 SqlSession 共享与失效机制
java·后端
考虑考虑1 小时前
JDK9中的Set.of()使用注意
java·后端·java ee
plainGeekDev2 小时前
findViewById → ViewBinding
java·kotlin·gradle
kkeeper~2 小时前
0基础C语言积跬步之自定义类型结构体
c语言·开发语言
yz_aiks2 小时前
IDEA终端配置oh-my-zsh实战:安装、插件与日常使用技巧
java·ide·intellij-idea