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、结语
在策略模式的进阶用法上,有几点要注意:
- 注解类、接口类、收集子类和实现类之间的对应关系是一对一对一对多。
- 注解类是区分实现类的重要依据,所以注解类上的区分字段的值一定是唯一的。
- 收集父类可以有多个子类,每个子类都代表着一个策略模式的使用。
- 注解类的属性值是在收集子类中被获取的。