前言
策略模式在日常开发经常使用,但是每个人的写法不一样,之前用枚举类写策略模式做充值卡推荐逻辑实现,大家看到这种写法,感觉很新奇,这里简单总结下策略模式的几种写法。我们简单以学生排序展示作为demo,前端根据age,name,score来进行排序展示。
经典写法
先定义一个排序接口
public interface SortStudent {
List<Student> sort(List<Student> studentSource);
}
根据年龄排序
/**
* @author 爱琴孩
*/
public class SortStudentByAge implements SortStudent {
@Override
public List<Student> sort(List<Student> studentSource) {
return studentSource.stream().sorted(Comparator.comparing(Student::getAge))
.collect(Collectors.toList());
}
}
根据成绩分数排序
/**
* @author 爱琴孩
*/
public class SortStudentByScore implements SortStudent {
@Override
public List<Student> sort(List<Student> studentSource) {
return studentSource.stream().sorted(Comparator.comparing(Student::getScore))
.collect(Collectors.toList());
}
}
根据姓名排序
/**
* @author 爱琴孩
*/
public class SortStudentByName implements SortStudent {
@Override
public List<Student> sort(List<Student> studentSource) {
return studentSource.stream().sorted(Comparator.comparing(Student::getName))
.collect(Collectors.toList());
}
}
构建一个上下文类
/**
* @author 爱琴孩
*/
public class SortStudentContext {
private SortStudent sortStudent;
public SortStudentContext(SortStudent sortStudent) {
this.sortStudent = sortStudent;
}
public SortStudentContext() {
}
public List<Student> sortStudent(List<Student> studentList) {
return sortStudent.sort(studentList);
}
}
业务调用方法
/**
* 经典策略模式
*
* @param sortType 排序模式
* @return java.util.List<com.example.study.model.Student>
* @author 爱琴孩
*/
public List<Student> testStrategy(String sortType) {
SortStudentContext sortStudentContext;
switch (sortType) {
case "sortByName":
sortStudentContext = new SortStudentContext(new SortStudentByName());
break;
case "sortByScore":
sortStudentContext = new SortStudentContext(new SortStudentByScore());
break;
default:
sortStudentContext = new SortStudentContext(new SortStudentByAge());
}
return sortStudentContext.sortStudent(students);
}
我们可以看到经典方法,创建了一个接口、三个策略类,还是比较啰嗦的。调用类的实现也待商榷,新增一个策略类还要修改榜单实例(可以用抽象工厂解决,但是复杂度又上升了)。加之我们有更好的选择,所以此处不再推荐经典策略模式。
枚举策略类写法
/**
* @author 爱琴孩
*/
public enum SortStudentEnum {
SortByAge {
@Override
public List<Student> sortStudents(List<Student> studentList) {
return studentList.stream().sorted(Comparator.comparing(Student::getAge)).
collect(Collectors.toList());
}
},
SortByName {
@Override
public List<Student> sortStudents(List<Student> studentList) {
return studentList.stream().sorted(Comparator.comparing(Student::getName)).
collect(Collectors.toList());
}
},
SortByScore {
@Override
public List<Student> sortStudents(List<Student> studentList) {
return studentList.stream().sorted(Comparator.comparing(Student::getScore)).
collect(Collectors.toList());
}
};
public abstract List<Student> sortStudents(List<Student> studentList);
}
业务调用方法
/**
* 枚举类策略模式
*
* @param sortType 排序模式
* @return java.util.List<com.example.study.model.Student>
* @author 爱琴孩
*/
public List<Student> testStrategyByEnum(String sortType) {
SortStudentEnum sortStudentEnum;
try {
sortStudentEnum = Enum.valueOf(SortStudentEnum.class, sortType);
} catch (IllegalArgumentException e) {
throw e;
}
return sortStudentEnum.sortStudents(students);
}
可以看到,如果策略简单的话,基于枚举的策略模式优雅许多,调用方也做到了0修改,但正确地使用枚举策略模式需要额外考虑以下几点。
- 枚举的策略类是公用且静态,这意味着这个策略过程不能引入非静态的部分,扩展性受限
- 策略模式的目标之一,是优秀的扩展性和可维护性,最好能新增或修改某一策略类时,对其他类是无改动的。而枚举策略如果过多或者过程复杂,维护是比较困难的,可维护性受限
基于工厂的策略模式
为了解决良好的扩展性和可维护性,我更推荐以下利用spring自带beanFactory的优势,实现一个基于工厂的策略模式。策略类改动只是添加了@Service注解,并指定了Service的value属性
/**
* @author 爱琴孩
*/
@Service("SortByAge")
public class SortStudentByAge implements SortStudent {
@Override
public List<Student> sort(List<Student> studentSource) {
return studentSource.stream().sorted(Comparator.comparing(Student::getAge))
.collect(Collectors.toList());
}
}
/**
* @author 爱琴孩
*/
@Service("SortByName")
public class SortStudentByName implements SortStudent {
@Override
public List<Student> sort(List<Student> studentSource) {
return studentSource.stream().sorted(Comparator.comparing(Student::getName))
.collect(Collectors.toList());
}
}
/**
* @author 爱琴孩
*/
@Service("SortByScore")
public class SortStudentByScore implements SortStudent {
@Override
public List<Student> sort(List<Student> studentSource) {
return studentSource.stream().sorted(Comparator.comparing(Student::getScore))
.collect(Collectors.toList());
}
}
业务调用方法
@Service
public class SpringTestService {
private static final Logger LOGGER = LoggerFactory.getLogger(SpringTestService.class);
private static List<Student> students;
@Resource
private Map<String, SortStudent> sortStudentMap;
static {
students = new ArrayList<Student>() {
{
add(new Student("霸都爱琴孩", 23, 99));
add(new Student("青山剑客", 26, 99));
add(new Student("西域刀郎", 43, 89));
add(new Student("西湖法海", 33, 98));
add(new Student("华山令狐冲", 22, 100));
}};
}
/**
* 通过Spring工厂来实现策略模式
* @author 爱琴孩
* @param sortType 排序类型
* @return java.util.List<com.example.study.model.Student>
*/
public List<Student> testStrategyByFactory(String sortType) {
if (!sortStudentMap.containsKey(sortType)) {
throw new ParameterException(RetCode.PARAM_ERROR.getCode());
}
LOGGER.info("sortStudentMap ={}", JSONObject.toJSON(sortStudentMap));
SortStudent sortStudent = sortStudentMap.get(sortType);
return sortStudent.sort(students);
}
}
@Resource
private Map<String, SortStudent> sortStudentMap;
上面这种写法使用了Spring的注解方式进行依赖注入。其中,@Resource注解用于标注需要注入的组件,Map<String, SortStudent> sortStudentMap则表示将Spring容器中所有类型为SortStudent的Bean注入到该Map中,并以Bean的name作为key存储。具体原理如下:
- Spring容器在启动时会将所有带有@Component、@Service、@Repository和@Controller注解的类实例化成Bean,并以Bean的name存储到容器中。
- 当使用@Resource注解进行依赖注入时,Spring通过类型匹配的方式,在容器中查找与该属性类型相同的Bean,将其注入到对应的属性中。
- 对于一个Map类型的属性,Spring将会将所有类型相同的Bean注入到该Map中,并以Bean的name作为key存储。
总结
经典模式显然不合适了,如果逻辑比较简单,比如说文中这种简单的排序展示,并且策略比较多的时候,选用枚举策略模式是很合适的,这样可以减少策略类,避免造成类泛滥。如果每个策略的业务逻辑比较复杂,策略类比较少,那么结合spring 的工厂模式是比较合适的。