Search code examples
javadependency-injectionfactory-pattern

What is a good way of telling low level objects which factories to use?


I've been learning a lot about Design Patterns lately, specifically Dependency Injection. I'm pretty sure that abstract factorys are a good way of instantiating objects that have dependencies. However I'm not sure how to tell lower level objects what factories they are supposed to use.

Consider following simplified example:

I have a class MainProgram (I just made this to represent that there is other code in my program..) At some point during runtime I want to instantiate a IGeneticAlgorithm with an abstract factory:

public class MainProgram{

    private AbstractGeneticAlgorithm geneticAlgorithm;
    private IGeneticAlgorithmFactory geneticAlgorithmFactory;

    public MainProgram(IGeneticAlgorithmFactory geneticAlgorithmFactory){
        this.geneticAlgorithmFactory = geneticAlgorithmFactory;
    }

    private void makeGeneticAlgorithm(){
        geneticAlgorithm = geneticAlgorithmFactory.getInstance();
    }

    public static void main(String[] args){
        MainProgram mainProgramm = new MainProgram(new FastGeneticAlgorithmFactory());
        //...
    }

}


public interface IGeneticAlgorithmFactory{
    public IGeneticAlgorithm getInstance();
}

public class FastGeneticAlgorithmFactory implements IGeneticAlgorithmFactory{
    public IGeneticAlgorithm getInstance(){
        return new FastGeneticAlgorithm();
    }
}


public abstract class AbstractGeneticAlgorithm{

    private IIndividual individual;
    private IIndividualFactory individualFactory;

    private void makeIndividual(){
        individual = individualFactory.getInstance();
    }

    //...
}

At some point during runtime I want to instantiate an IIndividual in my GeneticAlgorithm. The IIndividual can't be instantiated at startup. The need to be able to instantiate the IIndividual during runtime comes from the way Genetic Algorithms work, where basically after each Step of Selection-Recombination-Mutation new Individuals have to be instantiated. (For more information see https://en.wikipedia.org/wiki/Genetic_algorithm). I chose to give the AbstractGeneticAlgorithm here only one IIndividual to keep this example simple.

public class FastGeneticAlgorithm implements AbstractGeneticAlgorithm{

    private IIndividual individual; 
    private IIndividualFactory individualFactory;

}


public interface IIndividualFactory{
    public IIndividual getInstance();
}

public class SmallIndividualFactory implements IIndividualFactory{
    public IIndividual getInstance(){
        return new SmallIndividual();
    }

    //...
}


public interface IIndividual{
    //...
}

public class SmallIndividual implements IIndividual{
    //...
}

Making the SmallIndividualFactory a static variable in the FastGeneticAlgorithm doesn't seem to me like good practice. And passing the SmallIndividualFactory to Main, so that Main can pass it down to FastGeneticAlgorithm also doesn't seem right.

My question is how to solve this? Thank you.


Solution

  • When it comes to using Dependency Injection, the Abstract Factory pattern is often over-used. This doesn't mean that it's a bad pattern per se, but in many cases there are more suitable alternatives for the Abstract Factory pattern. This is described in detail in Dependency Injection Principles, Practices, and Patterns (paragraph 6.2) where is described that:

    • Abstract Factories should not be used to create short-lived, stateful dependencies, since a consumer of a dependency should be oblivious to its lifetime; from perspective of the consumer, there should conceptually be only one instance of a service.
    • Abstract Factories are often Dependency Inversion Principle (DIP) violations, because their design often doesn't suit the consumer, while the DIP states: "the abstracts are owned by the upper/policy layers", meaning that consumer of the abstraction should dictate its shape and define the abstraction in a way that suits its needs the most. Letting the consumer depend on both a factory dependency and the dependency it produces complicates the consumer.

    This means that:

    • Abstract Factories with a parameterless create method should be prevented, because it implies the dependency is short-lived and its lifetime is controlled by the consumer. Instead, Abstract Factories should be created for dependencies that conceptually require runtime data (provided by the consumer) to be created.
    • But even in case a factory method contains parameters, care must be taken to make sure that the Abstract Factory is really required. The Proxy pattern is often (but not always) better suited, because it allows the consumer to have a single dependency, instead of depending on both the factory and its product.

    Dependency Injection promotes composition of classes in the start-up path of the application, a concept the book refers to as the Composition Root. The Composition Root is a location close to that application's entry point (your Main method) and it knows about every other module in the system.

    Because the Composition Root takes a dependency on all other modules in the system, it typically makes little sense consume Abstract Factories within the Composition Root. For instance, in case you defined an IXFactory abstraction to produce IX dependencies, but the Composition Root is the sole consumer of the IXFactory abstraction, you are decoupling something that doesn't require decoupling: The Composition Root intrinsically knows about every other part of the system any way.

    This seems to be the case with your IGeneticAlgorithmFactory abstraction. Its sole consumer seems to be your Composition Root. If this is true, this abstraction and its implementation can simply be removed and the code within its getInstance method can simply be moved into the MainProgram class (which functions as your Composition Root).

    It's hard for me to understand whether or not your IIndividual implementations require a factory (it has been at least 14 years ago since I implemented a genetic algorithm at the University), but they seem more like runtime data rather than 'real' dependencies. So a factory might make sense here, although do verify whether their creation and implementation must be hidden behind an abstraction. I could imagine the application to be sufficiently loosely coupled when the FastGeneticAlgorithm creates SmallIndividual instances directly. This, however, is just a wild guess.

    On top of that, best practice is to apply Constructor Injection. This prevents Temporal Coupling. Furthermore, refrain specifying the implementations dependencies in the defined abstractions, as your AbstractGeneticAlgorithm does. This makes the abstraction a Leaky Abstraction (which is a DIP violation). Instead, declare the dependencies by declaring them as constructor arguments on the implementation (FastGeneticAlgorithm in your case).

    But even with the existence of the IIndividualFactory, your code can be simplified by following best practices as follows:

    // Use interfaces rather than base classes. Prefer Composition over Inheritance.
    public interface IGeneticAlgorithm { ... }
    public interface IIndividual { ... }
    public interface IIndividualFactory {
        public IIndividual getInstance();
    }
    
    // Implementations
    public class FastGeneticAlgorithm implements IGeneticAlgorithm {
        private IIndividualFactory individualFactory;
    
        // Use constructor injection to declare the implementation's dependencies
        public FastGeneticAlgorithm(IIndividualFactory individualFactory) {
            this.individualFactory = individualFactory;
        }
    }
    
    public class SmallIndividual implements IIndividual { }
    public class SmallIndividualFactory implements IIndividualFactory {
        public IIndividual getInstance() {
            return new SmallIndividual();
        }
    }
    
    public static class Program {
        public static void main(String[] args){
            AbstractGeneticAlgorithm algoritm = CreateAlgorithm();
            algoritm.makeIndividual();
        }
    
        private AbstractGeneticAlgorithm CreateAlgorithm() {
            // Build complete object graph inside the Composition Root
            return new FastGeneticAlgorithm(new SmallIndividualFactory());
        }
    }