在实际应用中,策略模式通常会搭配工厂模式使用。这篇博客将会介绍普通的工厂策略模式实现方式,以及如何结合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();
	}
}

整体来说,还是很容易实现的。其实,这种写法也没有什么大毛病。但你要是硬要挑毛病的话也是可以挑的。

  1. 需要手动将所有的策略类put到工厂中,如果策略类多要写的代码就多
  2. 如果增加或减少策略类,需要改动工厂类,不符合开闭原则

那接下来,我们就看如何结合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();

代码就简洁了很多。但很遗憾,事情并没有那么顺利,如果此时启动程序的话,控制台会报错:

java策略模式和工厂模式结合 spring 策略模式与工厂模式_java策略模式和工厂模式结合


这是因为Spring在注入像List,Map这样集合对象时,流程是这样的

  1. 从IOC容器中,找到所有符合类型的对象
  2. 将这些对象转换成List、Map等

    而Spring是默认提供了一些属性编辑器的:

    但很遗憾的是,并没有FactoryList对应的属性编辑器,因此,我们需要实现CustomCollectionEditor类,去添加一个自定义的属性编辑器,这样我们的进阶版策略工厂模式就算完成了。
public class FactoryListEditor extends CustomCollectionEditor {
    public FactoryListEditor() {
        super(FactoryArrayList.class);
        this.addPropertyChangeListener(new InitializingBeanListener());
    }
}

需要注意的是这个属性编辑器的名字是固定的,这是因为Spring的代码中写死了:

java策略模式和工厂模式结合 spring 策略模式与工厂模式_策略模式_02


转换的逻辑也很简单,就是把一个集合里的元素移到另外一个集合:

java策略模式和工厂模式结合 spring 策略模式与工厂模式_ide_03

注:本篇文章中,为了图方便,有少量代码是伪代码,无法直接复制运行