在实际应用中,策略模式通常会搭配工厂模式使用。这篇博客将会介绍普通的工厂策略模式实现方式,以及如何结合Spring使用策略工厂模式。
其中前俩种实现方式是比较简单的,第三种的话,就相对来说比较烧脑了,需要花时间研究一下。
普通工厂策略模式
案例很简单,就是有三个Animal 接口的实现类,希望通过传入不同的Type,调用对应实现类的sound方法。
策略类如下:
public interface Animal extends Match<String> {
void sound();
}
@Data
public class Cat implements Animal {
@Override
public void sound() {
System.out.println("喵喵喵");
}
}
@Data
public class Dog implements Animal {
@Override
public void sound() {
System.out.println("汪汪汪");
}
}
@Data
public class Pig implements Animal {
@Override
public void sound() {
System.out.println("咕噜咕噜咕噜");
}
}
使用工厂设计模式,管理策略
// 枚举类
public enum AnimalEnum {
DOG("dog"),
CAT("cat"),
PIG("pig");
private String kind;
AnimalEnum(String kind) {
this.kind = kind;
}
public String getKind() {
return kind;
}
public static AnimalEnum get(String kind) {
return Arrays.stream(values())
.filter(animalEnum -> animalEnum.getKind().equals(kind))
.findFirst()
.orElseThrow(() -> new RuntimeException("没有找到对应的策略枚举"));
}
}
// 工厂类
public class AnimalFactory {
private Map<AnimalEnum, Animal> animalMap = new HashMap<>();
public AnimalFalFactory() {lFactory() {ctory() {
animalMap.put(AnimalEnum.CAT, new Cat());
animalMap.put(AnimalEnum.PIG, new Pig());
animalMap.put(AnimalEnum.DOG, new Dog());
}
public Animal getBean(AnimalEnum kind) {
return animalMap.get(kind);
}
}
测试使用:
public class AnimalMain {
public static void main(String[] args) {
AnimalFactory animalFactory = new AnimalFactory();
animalFactory.getBean(AnimalEnum.DOG).sound();
}
}
整体来说,还是很容易实现的。其实,这种写法也没有什么大毛病。但你要是硬要挑毛病的话也是可以挑的。
- 需要手动将所有的策略类put到工厂中,如果策略类多要写的代码就多
- 如果增加或减少策略类,需要改动工厂类,不符合开闭原则
那接下来,我们就看如何结合Spring实现策略工厂模式,解决上面的俩个问题。
结合Spring使用策略模式
public interface Animal {
void sound();
AnimalEnum getType();
}
@Data
@Component
public class Cat implements Animal {
private AnimalEnum type = AnimalEnum.CAT;
@Override
public void sound() {
System.out.println("喵喵喵");
}
}
@Data
@Component
public class Dog implements Animal {
private AnimalEnum type = AnimalEnum.DOG;
@Override
public void sound() {
System.out.println("汪汪汪");
}
}
@Data
@Component
public class Pig implements Animal {
private AnimalEnum type = AnimalEnum.PIG;
@Override
public void sound() {
System.out.println("咕噜咕噜咕噜");
}
}
结合Spring的工厂模式:
@Component
public class AnimalFactory implements ApplicationContextAware {
private Map<AnimalEnum, Animal> animalFactory = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 从Spring容器中找到所有Animal的实现类(策略类),将他们放到工厂中
Map<String, Animal> animalMap = applicationContext.getBeansOfType(Animal.class);
animalMap.values().forEach(animal -> animalFactory.put(animal.getType(), animal));
}
public Animal getBean(AnimalEnum animalEnum) {
return animalFactory.get(animalEnum);
}
}
通过这种方式,我们就解决了上面提到的俩个问题。使用方式如下:
@RequestMapping
@RestController
public class AnimalFactoryController {
@Resource
private AnimalFactory animalFactory;
@GetMapping("call/{type}")
public void call(@PathVariable("type") String type) {
AnimalEnum kind = AnimalEnum.get(type);
animalFactory.getBean(kind).sound();
}
}
看样子已经没问题了,但我们硬要鸡蛋里挑骨头,假如我们的项目中,有多个不同的业务需要使用到策略模式,那么我们就需要为重复写多个工厂类。
有没有办法只一个通用的工厂类,共不同业务实现策略模式使用?借助Spring我们是可以实现的。
Spring策略工厂模式进阶版
首先,看一段代码:
@Resource
private List<Animal> animals;
在这段代码中,Spring会自动帮我们从IOC容器中找到所有的Animal实现类放到集合中。如果,我们的策略类提供一个Match方法,我们就可以从集合中过滤出我们想要的策略。通过这种方式,我们就不在需要写工厂类了。
// 定义Match接口,提供matching方法
public interface Match<T> {
boolean matching(T factor);
}
public interface Animal extends Match<AnimalEnum> {
void sound();
}
@Data
@Component
public class Cat implements Animal {
@Override
public void sound() {
System.out.println("喵喵喵");
}
@Override
public boolean matching(AnimalEnum type) {
return type == AnimalEnum.CAT;
}
}
@Component
@Data
public class Dog implements Animal {
@Override
public void sound() {
System.out.println("汪汪汪");
}
@Override
public boolean matching(AnimalEnum type) {
return type == AnimalEnum.DOG;
}
}
@Component
@Data
public class Pig implements Animal {
private AnimalEnum type = AnimalEnum.PIG;
@Override
public void sound() {
System.out.println("咕噜咕噜咕噜");
}
@Override
public boolean matching(AnimalEnum type) {
return type == AnimalEnum.PIG;
}
}
这样子,我们从工厂中获取策略类的代码就可以这么写:
@Resource
private List<Animal> animals;
// 过滤出我们想要的策略
animals.stream()
.filter(animal -> animal.matching(AnimalEnum.DOG))
.findFirst().get()
.sound();
但是,如果每次使用,都需要写一遍过滤代码的话就太麻烦了,于是我们自己写一个FactoryList接口,继承自List接口,并提供过滤策略的方法:
public interface FactoryList<K, E extends Match<K>> extends List<E> {
E getBean(K factor);
}
public class FactoryArrayList<K, E extends Match<K>> extends ArrayList<E>
implements FactoryList<K, E>, InitializingBean {
// 过滤策略
@Override
public E getBean(K factor) {
return stream()
.filter(e -> e.matching(factor))
.findFirst().orElseThrow(() -> new RuntimeException("没有找到对应的示例"));
}
}
如果顺利的话,接下来就是这么使用我们的策略工厂的:
@Resource
private FactoryList<AnimalEnum, Animal> animals;
animals.getBean(AnimalEnum.DOG).sound();
代码就简洁了很多。但很遗憾,事情并没有那么顺利,如果此时启动程序的话,控制台会报错:
这是因为Spring在注入像List,Map这样集合对象时,流程是这样的
- 从IOC容器中,找到所有符合类型的对象
- 将这些对象转换成List、Map等
而Spring是默认提供了一些属性编辑器的:
但很遗憾的是,并没有FactoryList对应的属性编辑器,因此,我们需要实现CustomCollectionEditor类,去添加一个自定义的属性编辑器,这样我们的进阶版策略工厂模式就算完成了。
public class FactoryListEditor extends CustomCollectionEditor {
public FactoryListEditor() {
super(FactoryArrayList.class);
this.addPropertyChangeListener(new InitializingBeanListener());
}
}
需要注意的是这个属性编辑器的名字是固定的,这是因为Spring的代码中写死了:
转换的逻辑也很简单,就是把一个集合里的元素移到另外一个集合:
注:本篇文章中,为了图方便,有少量代码是伪代码,无法直接复制运行