Module extending_generic_factory

Aedi, a dependency injection library.

Aedi is a dependency injection library. It does provide a set of containers that do IoC, and an interface to configure application components (structs, objects, etc.)

Aim

The aim of library is to provide a dependency injection solution that is feature rich, easy to use, easy to learn, and easy to extend up to your needs.

The interface presented in previous tutorial is a minimalistic interface, and does not provide any ability to customize the component that is created. It is practically a black box, that is convenient to use by containers, and not by client code that needs to configure components registered in container.

To support better configuration, without loosing black boxiness of presented Factory interface, the process of creating and configuring a component was split into three distinct steps below:

  1. Allocation and construction of component
  2. Configuration of component
  3. Destruct and deallocation of component

The following steps are formalized into a set of interfaces shown below, where each step itself is encapsulated into objects implementing presented interfaces.

interface PropertyConfigurer(T) : LocatorAware!() {

    public {

        void configure(ref T object);
    }
}

interface InstanceFactory(T) : LocatorAware!(), AllocatorAware!() {

    public {

        T factory();
    }
}

interface InstanceDestructor(T) : LocatorAware!(), AllocatorAware!() {

    public {

        void destruct(ref T destructable);
    }
}

interface GenericFactory(T) : Factory!T, InstanceFactoryAware!T, PropertyConfigurersAware!T {

    public {

        @property {

            alias locator = Factory!T.locator;

            Locator!() locator();
        }
    }
}

interface InstanceFactoryAware(T) {
    public {

        @property {
            InstanceFactoryAware!T setInstanceFactory(InstanceFactory!T factory);
        }
    }
}

interface InstanceDestructorAware(T) {
    public {

        @property {
            InstanceDestructorAware!T setInstanceDestructor(InstanceDestructor!T destructor);
        }
    }
}

interface PropertyConfigurersAware(T) {
    public {
        PropertyConfigurersAware!T addPropertyConfigurer(PropertyConfigurer!T configurer);
    }
}

The methods used to configure components in examples up to this point are actually wrappers over building blocks that implement interfaces presented above. register method is a wrapper over an implementation of GenericFactory interface.

When instantiation or configuration of a component is not possible to implement in terms of existing tools and interfaces provided by the framework, it is recommended to implement one of building block interface, depending on what stage the problem resides. For car simulation app, with- out reason the company decided that tires, should be instantiated using their own implementation of instance building block. Same with inflating a tire they've decided to use their own implementation, along with tire recycling, making a closed production cycle.

Next example shows a custom implementation of tire manufacturing process. By implementing InstanceFactory interface, the building block can be used along the rest of components already present in framework, such as GenericFactory. The only hinder is that what remains is the ugliness, and verbosity, due to need to manually instantiate the building block, and pass to generic factory. Instead of manually doing it, wrapping instantiator into a function will lessen the verbosity (check makeTire definition and usage), and increase the readability of configuration code, thanks to UFCS feature of D programming language.

auto makeTire(T : GenericFactory!Z, Z : Tire)(auto ref T factory, int size) {
    factory.setInstanceFactory(new TireConstructorInstanceFactory(size));

    return factory;
}

class TireConstructorInstanceFactory : InstanceFactory!Tire {

    private {
        int size;

        Locator!(Object, string) locator_;
        IAllocator allocator_;
    }

    public {
        this(int size) {
            this.size = size;
        }

        @property {
            TireConstructorInstanceFactory locator(Locator!(Object, string) locator) {
                this.locator_ = locator;

                return this;
            }

            TireConstructorInstanceFactory allocator(IAllocator allocator) pure nothrow @safe {
                this.allocator_ = allocator;

                return this;
            }
        }

        Tire factory() {
            Tire tire;

            import std.stdio;

            write("Creating a tire of ", size, " inches: ");
            tire = this.allocator_.make!Tire;
            tire.size = size;
            writeln("\t[..OK..]");

            return tire;
        }
    }
}

The code for tire inflator configurer can be seen in examples from source code. The difference from TireConstructorInstanceFactory is that implementation should extend PropertyConfigurer. Same can be said about tire destructor, it implements InstanceDestructor to provide destruction mechanisms for generic factory.

Once the custom implementation and their wrapper are implemented, it is possible to use them as any other built-in building blocks from framework. Next example shows how implemented building blocks can be used along with other configuration primitives.

void main() {

    SingletonContainer container = new SingletonContainer;
    scope (exit) container.terminate();

    with (container.configure) {

        register!Tire("logging.tire")
            .makeTire(17)
            .inflateTire(3.0)
            .set!"vendor"("hell tire")
            .destroyTire;
    }

    container.instantiate();

    container.locate!Tire("logging.tire").writeln;
}

Running it, produces following result:

Creating a tire of 17 inches: 	[..OK..]
Inflating tire to 3 atm: 	[..OK..]
Tire(17 inch, 3 atm, hell tire)
Destroying tire:        [..OK..]

Try to implement your own instance factory or property configurer, wrap them in a function, and use in component configuration. Try to do this for Car component from previous examples.

Functions

NameDescription
destroyTire(factory)
inflateTire(factory, pressure)
main()
makeTire(factory, size)

Classes

NameDescription
Tire
TireConstructorInstanceFactory
TireInflatorConfigurer
TireInstanceDestructor