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.

1 interface PropertyConfigurer(T) : LocatorAware!() {
2 
3     public {
4 
5         void configure(ref T object);
6     }
7 }
8 
9 interface InstanceFactory(T) : LocatorAware!(), AllocatorAware!() {
10 
11     public {
12 
13         T factory();
14     }
15 }
16 
17 interface InstanceDestructor(T) : LocatorAware!(), AllocatorAware!() {
18 
19     public {
20 
21         void destruct(ref T destructable);
22     }
23 }
24 
25 interface GenericFactory(T) : Factory!T, InstanceFactoryAware!T, PropertyConfigurersAware!T {
26 
27     public {
28 
29         @property {
30 
31             alias locator = Factory!T.locator;
32 
33             Locator!() locator();
34         }
35     }
36 }
37 
38 interface InstanceFactoryAware(T) {
39     public {
40 
41         @property {
42             InstanceFactoryAware!T setInstanceFactory(InstanceFactory!T factory);
43         }
44     }
45 }
46 
47 interface InstanceDestructorAware(T) {
48     public {
49 
50         @property {
51             InstanceDestructorAware!T setInstanceDestructor(InstanceDestructor!T destructor);
52         }
53     }
54 }
55 
56 interface PropertyConfigurersAware(T) {
57     public {
58         PropertyConfigurersAware!T addPropertyConfigurer(PropertyConfigurer!T configurer);
59     }
60 }

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.

1 auto makeTire(T : GenericFactory!Z, Z : Tire)(auto ref T factory, int size) {
2     factory.setInstanceFactory(new TireConstructorInstanceFactory(size));
3 
4     return factory;
5 }
6 
7 class TireConstructorInstanceFactory : InstanceFactory!Tire {
8 
9     private {
10         int size;
11 
12         Locator!(Object, string) locator_;
13         RCIAllocator allocator_;
14     }
15 
16     public {
17         this(int size) {
18             this.size = size;
19         }
20 
21         @property {
22             TireConstructorInstanceFactory locator(Locator!(Object, string) locator) {
23                 this.locator_ = locator;
24 
25                 return this;
26             }
27 
28             TireConstructorInstanceFactory allocator(RCIAllocator allocator) pure nothrow @safe {
29                 this.allocator_ = allocator;
30 
31                 return this;
32             }
33         }
34 
35         Tire factory() {
36             Tire tire;
37 
38             import std.stdio;
39 
40             write("Creating a tire of ", size, " inches: ");
41             tire = this.allocator_.make!Tire;
42             tire.size = size;
43             writeln("\t[..OK..]");
44 
45             return tire;
46         }
47     }
48 }

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.

1 void main() {
2 
3     SingletonContainer container = new SingletonContainer;
4     scope (exit) container.terminate();
5 
6     with (container.configure) {
7 
8         register!Tire("logging.tire")
9             .makeTire(17)
10             .inflateTire(3.0)
11             .set!"vendor"("hell tire")
12             .destroyTire;
13     }
14 
15     container.instantiate();
16 
17     container.locate!Tire("logging.tire").writeln;
18 }

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.

Meta

Authors

aermicioi

License

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following:

The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.