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 Extending factories tutorial explained that library can be extended in two ways,
13 factory direction and container direction. In AEDI framework containers have at least
14 two responsibilities. The first one is to serve components, and the second one is to
15 manage life time of served components.
16 
17 Due to the first responsibility, AEDI has two basic containers, singleton and prototype. The
18 behavior of those two containers can be refound in other implementations of DIF like Spring frame-
19 work. Singleton container will serve same component no matter how many times a caller asks
20 it for a component associated with desired identity. Prototype container will serve new instance of
21 component each time it is asked by caller. Switchable decorating container presented decorated containers tutorial
22 limits in some sense the lifetime of it’s components only to the period when it is enabled.
23 
24 A custom container should be implemented, only when existing ones are not providing features
25 required by the third party code (code that uses the framework). As stated, the purpose of a container
26 is to serve and manage components, and therefore a new implementation of a container should be
27 concerned with those tasks.
28 
29 Compared to implementing a factory component, implementing a new container, requires it
30 to implement several interfaces. Each interface provides some functionality that can be used by
31 framework to enable some additional features related to container functionality. Implementation of
32 those interfaces is not imposed. The only requirement is to implement the basic Container interface
33 that exposes component serving abilities, along with option to instantiate them before their actual
34 usage. The following interfaces are allowed to be implemented by custom container:
35 
36 $(UL
37     $(LI $(D_INLINECODE Container) – exposes serving component features.)
38     $(LI $(D_INLINECODE Storage) – exposes component factory storing capabilities.)
39     $(LI $(D_INLINECODE FactoryLocator) – exposes component factory locating capabilities)
40     $(LI $(D_INLINECODE AliasAware) – exposes aliasing of component’s identity to another identity)
41     $(LI $(D_INLINECODE ConfigurableContainer) – exposes component factory storing capabilities along FactoryLocator and AliasAware)
42 )
43 
44 Container:
45 
46 Container interface is the minimal requirement for a custom container to implement, since it
47 offers component serving capabilities. Listing below presents the interface that is to be implemented
48 by custom containers. It is meaningful to implement only this interface when, container will not
49 integrate with any advanced features that framework provides.
50 
51 ------------------
52 interface Container : Locator!(Object, string) {
53 
54     public {
55 
56         Container instantiate();
57 
58         Container terminate();
59     }
60 }
61 ------------------
62 
63 Storage:
64 
65 By implementing Storage interface, a container declares it’s ability to store component
66 factories in it. Listing below displays abilities required to be implemented by container. Once a
67 container implements the interface, the component registration system can use the container to store
68 factories in it. In other words it is possible to use $(D_INLINECODE register) method on custom container. The
69 interface does not enforce, how stored components are used internally by container. They can never
70 be used, or used for other purposes, rather than serving created components to caller. Though,
71 by convention it is assumed that stored factory components are used for right purpose of creating
72 components served by container, and deviating from this convention, will lead in ambiguity in code
73 api that must be documented exhaustive.
74 
75 ------------------
76 interface Storage(Type, KeyType) {
77 
78     public {
79 
80         Storage set(Type element, KeyType identity);
81 
82         Storage remove(KeyType identity);
83     }
84 }
85 ------------------
86 
87 FactoryLocator:
88 
89 FactoryLocator interface, implemented by a container, exposes ability for exterior code to
90 access stored factory components in a container. Listing below shows the abilities that a container must implement.
91 
92 Such functionality is useful for postprocessing of component factories after they are registered
93 into container, for various purposes. One of them, is dynamically register tagged components as a
94 dependency to an event emmiter component. The FactoryLocator functionality allows for third party
95 library developers, to implement more complicated and useful features based on postprocessing of
96 factory components.
97 
98 ------------------
99 interface FactoryLocator(T : Factory!Z, Z) {
100     import std.range.interfaces;
101     import std.typecons;
102 
103     public {
104 
105         T getFactory(string identity);
106 
107         InputRange!(Tuple!(T, string)) getFactories();
108     }
109 }
110 ------------------
111 
112 AliasAware:
113 
114 AliasAware implemented by a containers, exposes an interface to exterior by which aliasing
115 of identities becomes possible. Listing below shows the methods that a container should implement.
116 The aliasing of component identities can become handy, when a library provides a container with
117 components, and another third party code is reliant on a component from this container, yet the
118 dependency is referenced with another identity. Aliasing will allow the referenced identity to be
119 aliased to right identity of component. Such cases can occur when a component from a library is
120 deprecated and removed, and it’s functionality is provided by another one identified differently. In
121 the end the aliasing mechanism allows a component from container to be associated with multiple
122 identities.
123 
124 ------------------
125 interface AliasAware(Type) {
126 
127     public {
128 
129         AliasAware link(Type identity, Type alias_);
130 
131         AliasAware unlink(Type alias_);
132 
133         const(Type) resolve(in Type alias_) const;
134     }
135 }
136 ------------------
137 
138 ConfigurableContainer:
139 
140 ConfigurableContainer interface is amalgation of previosly defined interfaces. Instead of
141 declaring entire list of interfaces a container should implement, it is easier to replace it with ConfigurableCo
142 interface. Listing below shows the abilities that a container must implement
143 
144 ------------------
145 interface ConfigurableContainer : Container, Storage!(ObjectFactory, string), AliasAware!(string), FactoryLocator!ObjectFactory {
146 
147 }
148 ------------------
149 
150 Try to run example. Modify it, run it again to understand how to implement
151 your own container.
152 
153 License:
154 	Boost Software License - Version 1.0 - August 17th, 2003
155 
156 	Permission is hereby granted, free of charge, to any person or organization
157 	obtaining a copy of the software and accompanying documentation covered by
158 	this license (the "Software") to use, reproduce, display, distribute,
159 	execute, and transmit the Software, and to prepare derivative works of the
160 	Software, and to permit third-parties to whom the Software is furnished to
161 	do so, all subject to the following:
162 
163 	The copyright notices in the Software and this entire statement, including
164 	the above license grant, this restriction and the following disclaimer,
165 	must be included in all copies of the Software, in whole or in part, and
166 	all derivative works of the Software, unless such copies or derivative
167 	works are solely in the form of machine-executable object code generated by
168 	a source language processor.
169 
170 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
171 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
172 	FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
173 	SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
174 	FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
175 	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
176 	DEALINGS IN THE SOFTWARE.
177 
178 Authors:
179 	aermicioi
180 **/
181 
182 module extending_containers;
183 
184 import aermicioi.aedi;
185 import std.stdio;
186 import std.range;
187 import std.typecons;
188 
189 class MySingletonContainer : ConfigurableContainer {
190     import std.stdio;
191     private {
192 
193         Object[string] singletons;
194         ObjectFactory[string] factories;
195 
196         string[string] aliasings;
197         size_t levels;
198     }
199 
200     public {
201 
202         MySingletonContainer set(ObjectFactory object, string key) {
203 
204             write("Registering component by ", key, " of type ", object.type.toString(), ": ");
205             this.factories[key] = object;
206             writeln("[..OK..]");
207 
208             return this;
209         }
210 
211         MySingletonContainer remove(string key) {
212             this.factories.remove(key);
213             this.singletons.remove(key);
214 
215             return this;
216         }
217 
218         Object get(string key) {
219             writeln('\t'.repeat(levels), "Serving ", key, " component.");
220 
221             if ((this.resolve(key) in this.singletons) is null) {
222                 writeln('\t'.repeat(levels), key, " component not found.");
223 
224                 if ((this.resolve(key) in this.factories) is null) {
225                     throw new NotFoundException("Object with id " ~ key ~ " not found.");
226                 }
227 
228                 writeln('\t'.repeat(levels), "Instantiating ", key, " component {");
229                 levels++;
230 
231                 this.singletons[this.resolve(key)] = this.factories[this.resolve(key)].factory();
232                 levels--;
233                 writeln('\t'.repeat(levels), "}");
234             }
235 
236             return this.singletons[key];
237         }
238 
239         bool has(in string key) inout {
240             return (key in this.factories) !is null;
241         }
242 
243         MySingletonContainer instantiate() {
244             import std.algorithm : filter;
245 
246             writeln("Booting container");
247             foreach (pair; this.factories.byKeyValue.filter!((pair) => (pair.key in this.singletons) is null)) {
248 
249                 writeln("Instantiating component ", pair.key, " {");
250                 levels++;
251                 this.singletons[pair.key] = pair.value.factory;
252                 levels--;
253                 writeln("}");
254             }
255 
256             return this;
257         }
258 
259         MySingletonContainer terminate() {
260             import std.algorithm : filter;
261 
262             writeln("Shutting down container");
263             foreach (pair; this.factories.byKeyValue.filter!(pair => (pair.key in this.singletons) !is null)) {
264 
265                 writeln("Destroying component ", pair.key);
266                 pair.value.destruct(this.singletons[pair.key]);
267             }
268 
269             this.singletons.clear();
270 
271             return this;
272         }
273 
274         MySingletonContainer link(string key, string alias_) {
275             this.aliasings[alias_] = key;
276 
277             return this;
278         }
279 
280         MySingletonContainer unlink(string alias_) {
281             this.remove(alias_);
282 
283             return this;
284         }
285 
286         const(string) resolve(in string alias_) const {
287             import std.typecons : Rebindable;
288             Rebindable!(const(string)) aliased = alias_;
289 
290             while ((aliased in this.aliasings) !is null) {
291                 writeln("Resolving alias ", aliased, " to ", this.aliasings[aliased]);
292                 aliased = this.aliasings[aliased];
293             }
294 
295             return aliased;
296         }
297 
298 
299         ObjectFactory getFactory(string identity) {
300             return this.factories[identity];
301         }
302 
303         InputRange!(Tuple!(ObjectFactory, string)) getFactories() {
304             import std.algorithm;
305 
306             return this.factories.byKeyValue.map!(
307                 a => tuple(a.value, a.key)
308             ).inputRangeObject;
309         }
310     }
311 }
312 
313 /**
314 A struct that should be managed by container.
315 **/
316 struct Color {
317     ubyte r;
318     ubyte g;
319     ubyte b;
320 }
321 
322 /**
323 Size of a car.
324 **/
325 struct Size {
326 
327     ulong width;
328     ulong height;
329     ulong length;
330 }
331 
332 /**
333 Interface for engines.
334 
335 An engine should implement it, in order to be installable in a car.
336 **/
337 interface Engine {
338 
339     public {
340 
341         void turnOn();
342         void run();
343         void turnOff();
344         @property string vendor();
345     }
346 }
347 
348 /**
349 A concrete implementation of Engine that uses gasoline for propelling.
350 **/
351 class GasolineEngine : Engine {
352 
353     private {
354         string vendor_;
355     }
356 
357     public {
358         @property {
359         	GasolineEngine vendor(string vendor) @safe nothrow {
360         		this.vendor_ = vendor;
361 
362         		return this;
363         	}
364 
365         	string vendor() @safe nothrow {
366         		return this.vendor_;
367         	}
368         }
369 
370         void turnOn() {
371             writeln("pururukVrooomVrrr");
372 
373         }
374 
375         void run() {
376             writeln("vrooom");
377         }
378 
379         void turnOff() {
380             writeln("vrrrPrrft");
381         }
382     }
383 }
384 
385 /**
386 A concrete implementation of Engine that uses diesel for propelling.
387 **/
388 class DieselEngine : Engine {
389 
390     private {
391         string vendor_;
392     }
393 
394     public {
395         @property {
396         	DieselEngine vendor(string vendor) @safe nothrow {
397         		this.vendor_ = vendor;
398 
399         		return this;
400         	}
401 
402         	string vendor() @safe nothrow {
403         		return this.vendor_;
404         	}
405         }
406 
407         void turnOn() {
408             writeln("pururukVruumVrrr");
409 
410         }
411 
412         void run() {
413             writeln("vruum");
414         }
415 
416         void turnOff() {
417             writeln("vrrrPft");
418         }
419     }
420 }
421 
422 /**
423 A class representing a car.
424 **/
425 class Car {
426 
427     private {
428         Color color_; // Car color
429         Size size_; // Car size
430         Engine engine_; // Car engine
431     }
432 
433     public {
434 
435         /**
436         Constructor of car.
437 
438         Constructs a car with a set of sizes. A car cannot of course have
439         undefined sizes, so we should provide it during construction.
440 
441         Params:
442             size = size of a car.
443         **/
444         this(Size size, Engine engine) {
445             this.size_ = size;
446             this.engine = engine;
447         }
448 
449         @property {
450 
451             /**
452             Set color of car
453 
454             Set color of car. A car can live with undefined color (physics allow it).
455 
456             Params:
457             	color = color of a car.
458 
459             Returns:
460             	Car
461             **/
462             Car color(Color color) @safe nothrow {
463             	this.color_ = color;
464 
465             	return this;
466             }
467 
468             Color color() @safe nothrow {
469             	return this.color_;
470             }
471 
472             Size size() @safe nothrow {
473             	return this.size_;
474             }
475 
476             /**
477             Set engine used in car.
478 
479             Params:
480             	engine = engine used in car to propel it.
481 
482             Returns:
483             	Car
484             **/
485             Car engine(Engine engine) @safe nothrow {
486             	this.engine_ = engine;
487 
488             	return this;
489             }
490 
491             Engine engine() @safe nothrow {
492             	return this.engine_;
493             }
494         }
495 
496         void start() {
497             engine.turnOn();
498         }
499 
500         void run() {
501             engine.run();
502         }
503 
504         void stop() {
505             engine.turnOff();
506         }
507     }
508 }
509 
510 void drive(Car car, string name) {
511     writeln("Uuh, what a nice car, ", name," with following specs:");
512     writeln("Size:", car.size());
513     writeln("Color:", car.color());
514 
515     writeln("Let's turn it on");
516     car.start();
517 
518     writeln("What a nice sound! We should make a test drive!");
519     car.run();
520 
521     writeln("Umm the test drive was awesome, let's get home and turn it off.");
522     car.run();
523     car.stop();
524 }
525 
526 void main() {
527 
528     MySingletonContainer container = new MySingletonContainer;
529     scope(exit) container.terminate();
530 
531     with (container.configure) {
532 
533         register!Color("color.green") // Register "green" color into container.
534             .set!"r"(cast(ubyte) 0)
535             .set!"g"(cast(ubyte) 255)
536             .set!"b"(cast(ubyte) 0);
537 
538         register!Size("size.sedan") // Register a size of a generic "sedan" into container
539             .set!"width"(200UL)
540             .set!"height"(150UL)
541             .set!"length"(500UL);
542 
543         register!(Engine, GasolineEngine)
544             .set!"vendor"("Mundane motors"); // Register a gasoline engine as default implementation of an engine
545         register!GasolineEngine
546             .set!"vendor"("Elite motors"); // Register a gasoline engine. Note: this engine is not the same gasoline engine from default implementation.
547         register!DieselEngine
548             .set!"vendor"("Hardcore motors"); // Register a diesel engine
549 
550         register!Car("sedan.engine.default") // Register a car with a default engine
551             .construct("size.sedan".lref, lref!Engine)
552             .set!"color"("color.green".lref);
553 
554         register!Car("sedan.engine.gasoline") // Register a car with gasoline engine
555             .construct("size.sedan".lref, lref!GasolineEngine)
556             .set!"color"("color.green".lref);
557 
558         register!Car("sedan.engine.diesel") // Register a car with diesel engine
559             .construct("size.sedan".lref, lref!DieselEngine)
560             .set!"color"("color.green".lref);
561 
562     }
563 
564     container.instantiate(); // Boot container (or prepare managed code/data).
565 
566     container.locate!Car("sedan.engine.default").drive("Default car");
567     container.locate!Car("sedan.engine.gasoline").drive("Gasoline car");
568     container.locate!Car("sedan.engine.diesel").drive("Diesel car");
569 }