1 /**
2 Aedi, a dependency injection library.
3 
4 Aedi is a dependency injection library. It does provide a set of containers that do
5 IoC, and an interface to configure application components (structs, objects, etc.)
6 
7 Aim:
8 
9 The aim of library is to provide a dependency injection solution that is
10 feature rich, easy to use, easy to learn, and easy to extend up to your needs.
11 
12 The interface presented in previous tutorial is a minimalistic interface, and does not provide any ability to
13 customize the component that is created. It is practically a black box, that is convenient to use by
14 containers, and not by client code that needs to configure components registered in container.
15 
16 To support better configuration, without loosing black boxiness of presented $(D_INLINECODE Factory) interface,
17 the process of creating and configuring a component was split into three distinct steps below:
18 $(OL
19     $(LI Allocation and construction of component )
20     $(LI Configuration of component )
21     $(LI Destruct and deallocation of component)
22     )
23 
24 The following steps are formalized into a set of interfaces shown below, where each step
25 itself is encapsulated into objects implementing presented interfaces.
26 
27 --------------------
28 interface PropertyConfigurer(T) : LocatorAware!() {
29 
30     public {
31 
32         void configure(ref T object);
33     }
34 }
35 
36 interface InstanceFactory(T) : LocatorAware!(), AllocatorAware!() {
37 
38     public {
39 
40         T factory();
41     }
42 }
43 
44 interface InstanceDestructor(T) : LocatorAware!(), AllocatorAware!() {
45 
46     public {
47 
48         void destruct(ref T destructable);
49     }
50 }
51 
52 interface GenericFactory(T) : Factory!T, InstanceFactoryAware!T, PropertyConfigurersAware!T {
53 
54     public {
55 
56         @property {
57 
58             alias locator = Factory!T.locator;
59 
60             Locator!() locator();
61         }
62     }
63 }
64 
65 interface InstanceFactoryAware(T) {
66     public {
67 
68         @property {
69             InstanceFactoryAware!T setInstanceFactory(InstanceFactory!T factory);
70         }
71     }
72 }
73 
74 interface InstanceDestructorAware(T) {
75     public {
76 
77         @property {
78             InstanceDestructorAware!T setInstanceDestructor(InstanceDestructor!T destructor);
79         }
80     }
81 }
82 
83 interface PropertyConfigurersAware(T) {
84     public {
85         PropertyConfigurersAware!T addPropertyConfigurer(PropertyConfigurer!T configurer);
86     }
87 }
88 --------------------
89 
90 The methods used to configure components in examples up to this point are actually wrappers
91 over building blocks that implement interfaces presented above. $(D_INLINECODE register) method is a
92 wrapper over an implementation of $(D_INLINECODE GenericFactory) interface.
93 
94 When instantiation or configuration of a component is not possible to implement in terms of
95 existing tools and interfaces provided by the framework, it is recommended to implement one of
96 building block interface, depending on what stage the problem resides. For car simulation app, with-
97 out reason the company decided that tires, should be instantiated using their own implementation of
98 instance building block. Same with inflating a tire they've decided to use their own implementation,
99 along with tire recycling, making a closed production cycle.
100 
101 Next example shows a custom implementation of tire manufacturing process. By implementing
102 $(D_INLINECODE InstanceFactory) interface, the building block can be used along the rest of components already
103 present in framework, such as $(D_INLINECODE GenericFactory). The only hinder is that what remains is the ugliness, and
104 verbosity, due to need to manually instantiate the building block, and pass to generic factory. Instead
105 of manually doing it, wrapping instantiator into a function will lessen the verbosity (check $(D_INLINECODE makeTire)
106 definition and usage), and increase the readability of configuration code, thanks to UFCS feature of
107 D programming language.
108 
109 --------------------
110 auto makeTire(T : GenericFactory!Z, Z : Tire)(auto ref T factory, int size) {
111     factory.setInstanceFactory(new TireConstructorInstanceFactory(size));
112 
113     return factory;
114 }
115 
116 class TireConstructorInstanceFactory : InstanceFactory!Tire {
117 
118     private {
119         int size;
120 
121         Locator!(Object, string) locator_;
122         RCIAllocator allocator_;
123     }
124 
125     public {
126         this(int size) {
127             this.size = size;
128         }
129 
130         @property {
131             TireConstructorInstanceFactory locator(Locator!(Object, string) locator) {
132                 this.locator_ = locator;
133 
134                 return this;
135             }
136 
137             TireConstructorInstanceFactory allocator(RCIAllocator allocator) pure nothrow @safe {
138                 this.allocator_ = allocator;
139 
140                 return this;
141             }
142         }
143 
144         Tire factory() {
145             Tire tire;
146 
147             import std.stdio;
148 
149             write("Creating a tire of ", size, " inches: ");
150             tire = this.allocator_.make!Tire;
151             tire.size = size;
152             writeln("\t[..OK..]");
153 
154             return tire;
155         }
156     }
157 }
158 --------------------
159 
160 The code for tire inflator configurer can be seen in examples from source code. The difference
161 from $(D_INLINECODE TireConstructorInstanceFactory) is that implementation should extend $(D_INLINECODE PropertyConfigurer).
162 Same can be said about tire destructor, it implements $(D_INLINECODE InstanceDestructor) to provide
163 destruction mechanisms for generic factory.
164 
165 Once the custom implementation and their wrapper are implemented, it is possible to use them
166 as any other built-in building blocks from framework. Next example shows how implemented building
167 blocks can be used along with other configuration primitives.
168 --------------------
169 void main() {
170 
171     SingletonContainer container = new SingletonContainer;
172     scope (exit) container.terminate();
173 
174     with (container.configure) {
175 
176         register!Tire("logging.tire")
177             .makeTire(17)
178             .inflateTire(3.0)
179             .set!"vendor"("hell tire")
180             .destroyTire;
181     }
182 
183     container.instantiate();
184 
185     container.locate!Tire("logging.tire").writeln;
186 }
187 --------------------
188 
189 Running it, produces following result:
190 --------------------
191 Creating a tire of 17 inches: 	[..OK..]
192 Inflating tire to 3 atm: 	[..OK..]
193 Tire(17 inch, 3 atm, hell tire)
194 Destroying tire:        [..OK..]
195 --------------------
196 
197 Try to implement your own instance factory or property configurer, wrap them
198 in a function, and use in component configuration. Try to do this for $(D_INLINECODE Car)
199 component from previous examples.
200 
201 License:
202 	Boost Software License - Version 1.0 - August 17th, 2003
203 
204 	Permission is hereby granted, free of charge, to any person or organization
205 	obtaining a copy of the software and accompanying documentation covered by
206 	this license (the "Software") to use, reproduce, display, distribute,
207 	execute, and transmit the Software, and to prepare derivative works of the
208 	Software, and to permit third-parties to whom the Software is furnished to
209 	do so, all subject to the following:
210 
211 	The copyright notices in the Software and this entire statement, including
212 	the above license grant, this restriction and the following disclaimer,
213 	must be included in all copies of the Software, in whole or in part, and
214 	all derivative works of the Software, unless such copies or derivative
215 	works are solely in the form of machine-executable object code generated by
216 	a source language processor.
217 
218 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
219 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
220 	FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
221 	SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
222 	FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
223 	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
224 	DEALINGS IN THE SOFTWARE.
225 
226 Authors:
227 	aermicioi
228 **/
229 
230 module extending_generic_factory;
231 
232 import aermicioi.aedi;
233 import std.stdio;
234 import std.experimental.allocator;
235 
236 class TireConstructorInstanceFactory : InstanceFactory!Tire {
237 
238     private {
239         int size;
240 
241         Locator!(Object, string) locator_;
242         RCIAllocator allocator_;
243     }
244 
245     public {
246         this(int size) {
247             this.size = size;
248         }
249 
250         @property {
251             TireConstructorInstanceFactory locator(Locator!(Object, string) locator) {
252                 this.locator_ = locator;
253 
254                 return this;
255             }
256 
257             TireConstructorInstanceFactory allocator(RCIAllocator allocator) pure nothrow @safe {
258                 this.allocator_ = allocator;
259 
260                 return this;
261             }
262         }
263 
264         Tire factory() {
265             Tire tire;
266 
267             import std.stdio;
268 
269             write("Creating a tire of ", size, " inches: ");
270             tire = this.allocator_.make!Tire;
271             tire.size = size;
272             writeln("\t[..OK..]");
273 
274             return tire;
275         }
276     }
277 }
278 
279 class TireInflatorConfigurer : PropertyConfigurer!Tire {
280 
281     private {
282         float atmospheres;
283         Locator!() locator_;
284     }
285 
286     public {
287 
288         this(float atmospheres = 3.0) {
289             this.atmospheres = atmospheres;
290         }
291 
292         @property {
293             TireInflatorConfigurer locator(Locator!(Object, string) locator) {
294                 this.locator_ = locator;
295 
296                 return this;
297             }
298         }
299 
300         void configure(ref Tire tire) {
301 
302             import std.stdio;
303 
304             write("Inflating tire to ", atmospheres, " atm: ");
305             tire.pressure = atmospheres;
306             writeln("\t[..OK..]");
307         }
308     }
309 }
310 
311 class TireInstanceDestructor : InstanceDestructor!Tire {
312     mixin LocatorAwareMixin!TireInstanceDestructor;
313     mixin AllocatorAwareMixin!TireInstanceDestructor;
314 
315     public {
316         void destruct(ref Tire destructable) {
317             write("Destroying tire: ");
318             this.allocator.dispose(destructable);
319             writeln("\t[..OK..]");
320         }
321     }
322 }
323 
324 auto makeTire(T : GenericFactory!Z, Z : Tire)(auto ref T factory, int size) {
325     factory.setInstanceFactory(new TireConstructorInstanceFactory(size));
326 
327     return factory;
328 }
329 
330 auto inflateTire(T : GenericFactory!Z, Z : Tire)(auto ref T factory, float pressure) {
331     factory.addPropertyConfigurer(new TireInflatorConfigurer(pressure));
332 
333     return factory;
334 }
335 
336 auto destroyTire(T : GenericFactory!Z, Z : Tire)(auto ref T factory) {
337     factory.setInstanceDestructor(new TireInstanceDestructor());
338 
339     return factory;
340 }
341 
342 class Tire {
343     private {
344         int size_;
345         float pressure_;
346         string vendor_;
347     }
348 
349     public @property {
350         Tire size(int size) @safe nothrow {
351         	this.size_ = size;
352 
353         	return this;
354         }
355 
356         int size() @safe nothrow {
357         	return this.size_;
358         }
359 
360         Tire pressure(float pressure) @safe nothrow {
361         	this.pressure_ = pressure;
362 
363         	return this;
364         }
365 
366         float pressure() @safe nothrow {
367         	return this.pressure_;
368         }
369 
370         Tire vendor(string vendor) @safe nothrow {
371         	this.vendor_ = vendor;
372 
373         	return this;
374         }
375 
376         string vendor() @safe nothrow {
377         	return this.vendor_;
378         }
379     }
380 
381     public override string toString() {
382         import std.algorithm;
383         import std.range;
384         import std.conv;
385         import std.utf;
386 
387         return only("Tire(", this.size.to!string, " inch, ", this.pressure.to!string, " atm, ", this.vendor, ")")
388             .joiner
389             .byChar
390             .array;
391     }
392 }
393 
394 void main() {
395 
396     SingletonContainer container = new SingletonContainer;
397     scope (exit) container.terminate();
398 
399     with (container.configure) {
400 
401         register!Tire("logging.tire")
402             .makeTire(17)
403             .inflateTire(3.0)
404             .set!"vendor"("hell tire")
405             .destroyTire;
406     }
407 
408     container.instantiate();
409 
410     container.locate!Tire("logging.tire").writeln;
411 }