DEV Community 👩‍💻👨‍💻

DEV Community 👩‍💻👨‍💻 is a community of 968,873 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Factory Pattern with Polymorphism on Spring
Jonatas de Moraes Junior
Jonatas de Moraes Junior

Posted on

Factory Pattern with Polymorphism on Spring

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!";
    }
}
Enter fullscreen mode Exit fullscreen mode

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!";
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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)

🌚 Browsing with dark mode makes you a better developer.

It's a scientific fact.