The Factory Pattern is one of the most well-known design patterns described by the Gang of Four (GoF). It allows for the caller to choose wich type of class needs to be instantiated, also hiding the creation logic from the caller.
However, there is a very confusing Spring mehcanic available through this here FactoryBean. Event though it has Factory in its name, it does not represent a Factory as designed by GoF, because it only allows for the creation of a single type.
In this article we will explore how to implement a full-compliant GoF Factory with Spring, where the created objects are also Spring controlled beans.
Let's say you have an interface Animal and two implementations Cat and Dog:
public interface Animal {
String makeNoise();
}
public class Cat implements Animal {
@Override
public String makeNoise() {
return "Meow!";
}
}
public class Dog implements Animal {
@Override
public String makeNoise() {
return "Bark!";
}
}
By definition: "Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created".
Ok, boring. However, what you expect is an AnimalFactory class that allows you to choose wether to get an instance of either a Cat or a Dog, right?
You could create a simple AnimalFactory class which just returns a new Cat or new Dog depending on a parameter you pass. The problem is: when you call new by yourself, the bean is not being created by Spring.
So, let's start by just doing that, let's show these classes to Spring and let it control their instances. Let's annotate them with @Component:
@Component
public class Cat implements Animal {
@Override
public String makeNoise() {
return "Meow!";
}
}
@Component
public class Dog implements Animal {
@Override
public String makeNoise() {
return "Bark!";
}
}
Now Spring knows Cat and Dog, and can give us instances of them when requested. Let's now create a very simple Factory that just fulfills our requirements:
@Component
public class AnimalFactory {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
public Animal getAnimal(String animalType) {
if ("cat".equals(animalType)) {
return this.cat;
} else if ("dog".equals(animalType)) {
return this.dog;
} else {
return null;
}
}
}
Ok, it works. But what if you have a thousand different animals? Let's improve this factory a bit by putting the instances in a Map:
public enum AnimalType {
CAT,
DOG,
;
}
@Component
public class AnimalFactory {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private EnumMap<AnimalType, Animal> animalsMap;
public AnimalFactory() {
this.animalsMap = new EnumMap<>(AnimalType.class);
this.animalsMap.put(AnimalType.CAT, cat);
this.animalsMap.put(AnimalType.DOG, dog);
}
public Animal getAnimal(AnimalType animalType) {
return this.animalsMap.get(animalType);
}
}
This time we have created an Enum with the types of the animals, and also a EnumMap to contain the instances of the animals. We also have changed the signature of the getAnimal method to take the enum as parameter.
This is a much better approach, but still, if we have a huge number of animal types, this class will grow accordingly with nothing but boilerplate code. And if you create a new animal type and forget to put it in the map, you may get an error somewhere else.
To apply the final touches to our Factory, we will make use of a lesser known functionality of Spring: an @Autowired constructor that takes a List of objects as parameter. Since Spring knows all Animal objects, when we inject a list of that type, Spring populates that List with all known objects of that type (Cat and Dog). Below is the full final code:
public enum AnimalType {
CAT,
DOG,
;
}
public interface Animal {
AnimalType getType();
String makeNoise();
}
@Component
public class Cat implements Animal {
@Override
public AnimalType getType() {
return AnimalType.CAT;
}
@Override
public String makeNoise() {
return "Meow!";
}
}
@Component
public class Dog implements Animal {
@Override
public AnimalType getType() {
return AnimalType.DOG;
}
@Override
public String makeNoise() {
return "Bark!";
}
}
@Component
public class AnimalFactory {
private EnumMap<AnimalType, Animal> animalsMap;
@Autowired
public AnimalFactory(List<Animal> animals) {
this.animalsMap = new EnumMap<>(AnimalType.class);
for (Animal animal: animals) {
this.animalsMap.put(animal.getType(), animal);
}
}
public Animal getAnimal(AnimalType animalType) {
return this.animalsMap.get(animalType);
}
}
Here we have created a new method in the Animal interface: getType(), that return that animal's type. At the factory's constructor, we take the List of Animals and populate our map with them. This way the code for the AnimalFactory class does not need to be changed with every new Animal created; whenever you need a new Animal, just annotate it with @Component and the Factory can create it.
This is it, I hope it helps.
Top comments (0)