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 aermicioi.aedi.util.typecons : Pair, pair; 186 import std.stdio; 187 import std.range; 188 import std.typecons; 189 190 @safe class MySingletonContainer : ConfigurableContainer { 191 import std.stdio; 192 private { 193 194 Object[string] singletons; 195 ObjectFactory[string] factories; 196 197 string[string] aliasings; 198 size_t levels; 199 } 200 201 public { 202 203 MySingletonContainer set(ObjectFactory object, string key) { 204 205 write("Registering component by ", key, " of type ", object.type.toString(), ": "); 206 this.factories[key] = object; 207 writeln("[..OK..]"); 208 209 return this; 210 } 211 212 MySingletonContainer remove(string key) { 213 this.factories.remove(key); 214 this.singletons.remove(key); 215 216 return this; 217 } 218 219 Object get(string key) { 220 writeln('\t'.repeat(levels), "Serving ", key, " component."); 221 222 if ((this.resolve(key) in this.singletons) is null) { 223 writeln('\t'.repeat(levels), key, " component not found."); 224 225 if ((this.resolve(key) in this.factories) is null) { 226 throw new NotFoundException("Object with id ${identity} not found.", key); 227 } 228 229 writeln('\t'.repeat(levels), "Instantiating ", key, " component {"); 230 levels++; 231 232 this.singletons[this.resolve(key)] = this.factories[this.resolve(key)].factory(); 233 levels--; 234 writeln('\t'.repeat(levels), "}"); 235 } 236 237 return this.singletons[key]; 238 } 239 240 bool has(in string key) inout { 241 return (key in this.factories) !is null; 242 } 243 244 MySingletonContainer instantiate() { 245 import std.algorithm : filter; 246 247 writeln("Booting container"); 248 foreach (pair; this.factories.byKeyValue.filter!((pair) => (pair.key in this.singletons) is null)) { 249 250 writeln("Instantiating component ", pair.key, " {"); 251 levels++; 252 this.singletons[pair.key] = pair.value.factory; 253 levels--; 254 writeln("}"); 255 } 256 257 return this; 258 } 259 260 MySingletonContainer terminate() { 261 import std.algorithm : filter; 262 263 writeln("Shutting down container"); 264 foreach (pair; this.factories.byKeyValue.filter!(pair => (pair.key in this.singletons) !is null)) { 265 266 writeln("Destroying component ", pair.key); 267 pair.value.destruct(this.singletons[pair.key]); 268 } 269 270 (() @trusted => this.singletons.clear())(); 271 272 return this; 273 } 274 275 MySingletonContainer link(string key, string alias_) { 276 this.aliasings[alias_] = key; 277 278 return this; 279 } 280 281 MySingletonContainer unlink(string alias_) { 282 this.remove(alias_); 283 284 return this; 285 } 286 287 const(string) resolve(in string alias_) const { 288 import std.typecons : Rebindable; 289 Rebindable!(const(string)) aliased = alias_; 290 291 while ((aliased in this.aliasings) !is null) { 292 writeln("Resolving alias ", aliased, " to ", this.aliasings[aliased]); 293 aliased = this.aliasings[aliased]; 294 } 295 296 return aliased; 297 } 298 299 300 ObjectFactory getFactory(string identity) { 301 return this.factories[identity]; 302 } 303 304 InputRange!(Pair!(ObjectFactory, string)) getFactories() { 305 import std.algorithm; 306 307 return this.factories.byKeyValue.map!( 308 a => pair(a.value, a.key) 309 ).inputRangeObject; 310 } 311 } 312 } 313 314 /** 315 A struct that should be managed by container. 316 **/ 317 struct Color { 318 ubyte r; 319 ubyte g; 320 ubyte b; 321 } 322 323 /** 324 Size of a car. 325 **/ 326 struct Size { 327 328 ulong width; 329 ulong height; 330 ulong length; 331 } 332 333 /** 334 Interface for engines. 335 336 An engine should implement it, in order to be installable in a car. 337 **/ 338 interface Engine { 339 340 public { 341 342 void turnOn(); 343 void run(); 344 void turnOff(); 345 @property string vendor(); 346 } 347 } 348 349 /** 350 A concrete implementation of Engine that uses gasoline for propelling. 351 **/ 352 class GasolineEngine : Engine { 353 354 private { 355 string vendor_; 356 } 357 358 public { 359 @property { 360 GasolineEngine vendor(string vendor) @safe nothrow { 361 this.vendor_ = vendor; 362 363 return this; 364 } 365 366 string vendor() @safe nothrow { 367 return this.vendor_; 368 } 369 } 370 371 void turnOn() { 372 writeln("pururukVrooomVrrr"); 373 374 } 375 376 void run() { 377 writeln("vrooom"); 378 } 379 380 void turnOff() { 381 writeln("vrrrPrrft"); 382 } 383 } 384 } 385 386 /** 387 A concrete implementation of Engine that uses diesel for propelling. 388 **/ 389 class DieselEngine : Engine { 390 391 private { 392 string vendor_; 393 } 394 395 public { 396 @property { 397 DieselEngine vendor(string vendor) @safe nothrow { 398 this.vendor_ = vendor; 399 400 return this; 401 } 402 403 string vendor() @safe nothrow { 404 return this.vendor_; 405 } 406 } 407 408 void turnOn() { 409 writeln("pururukVruumVrrr"); 410 411 } 412 413 void run() { 414 writeln("vruum"); 415 } 416 417 void turnOff() { 418 writeln("vrrrPft"); 419 } 420 } 421 } 422 423 /** 424 A class representing a car. 425 **/ 426 class Car { 427 428 private { 429 Color color_; // Car color 430 Size size_; // Car size 431 Engine engine_; // Car engine 432 } 433 434 public { 435 436 /** 437 Constructor of car. 438 439 Constructs a car with a set of sizes. A car cannot of course have 440 undefined sizes, so we should provide it during construction. 441 442 Params: 443 size = size of a car. 444 **/ 445 this(Size size, Engine engine) { 446 this.size_ = size; 447 this.engine = engine; 448 } 449 450 @property { 451 452 /** 453 Set color of car 454 455 Set color of car. A car can live with undefined color (physics allow it). 456 457 Params: 458 color = color of a car. 459 460 Returns: 461 Car 462 **/ 463 Car color(Color color) @safe nothrow { 464 this.color_ = color; 465 466 return this; 467 } 468 469 Color color() @safe nothrow { 470 return this.color_; 471 } 472 473 Size size() @safe nothrow { 474 return this.size_; 475 } 476 477 /** 478 Set engine used in car. 479 480 Params: 481 engine = engine used in car to propel it. 482 483 Returns: 484 Car 485 **/ 486 Car engine(Engine engine) @safe nothrow { 487 this.engine_ = engine; 488 489 return this; 490 } 491 492 Engine engine() @safe nothrow { 493 return this.engine_; 494 } 495 } 496 497 void start() { 498 engine.turnOn(); 499 } 500 501 void run() { 502 engine.run(); 503 } 504 505 void stop() { 506 engine.turnOff(); 507 } 508 } 509 } 510 511 void drive(Car car, string name) { 512 writeln("Uuh, what a nice car, ", name," with following specs:"); 513 writeln("Size:", car.size()); 514 writeln("Color:", car.color()); 515 516 writeln("Let's turn it on"); 517 car.start(); 518 519 writeln("What a nice sound! We should make a test drive!"); 520 car.run(); 521 522 writeln("Umm the test drive was awesome, let's get home and turn it off."); 523 car.run(); 524 car.stop(); 525 } 526 527 void main() { 528 529 MySingletonContainer container = new MySingletonContainer; 530 scope(exit) container.terminate(); 531 532 with (container.configure) { 533 534 register!Color("color.green") // Register "green" color into container. 535 .set!"r"(cast(ubyte) 0) 536 .set!"g"(cast(ubyte) 255) 537 .set!"b"(cast(ubyte) 0); 538 539 register!Size("size.sedan") // Register a size of a generic "sedan" into container 540 .set!"width"(200UL) 541 .set!"height"(150UL) 542 .set!"length"(500UL); 543 544 register!(Engine, GasolineEngine) 545 .set!"vendor"("Mundane motors"); // Register a gasoline engine as default implementation of an engine 546 register!GasolineEngine 547 .set!"vendor"("Elite motors"); // Register a gasoline engine. Note: this engine is not the same gasoline engine from default implementation. 548 register!DieselEngine 549 .set!"vendor"("Hardcore motors"); // Register a diesel engine 550 551 register!Car("sedan.engine.default") // Register a car with a default engine 552 .construct("size.sedan".lref, lref!Engine) 553 .set!"color"("color.green".lref); 554 555 register!Car("sedan.engine.gasoline") // Register a car with gasoline engine 556 .construct("size.sedan".lref, lref!GasolineEngine) 557 .set!"color"("color.green".lref); 558 559 register!Car("sedan.engine.diesel") // Register a car with diesel engine 560 .construct("size.sedan".lref, lref!DieselEngine) 561 .set!"color"("color.green".lref); 562 563 } 564 565 container.instantiate(); // Boot container (or prepare managed code/data). 566 567 container.locate!Car("sedan.engine.default").drive("Default car"); 568 container.locate!Car("sedan.engine.gasoline").drive("Gasoline car"); 569 container.locate!Car("sedan.engine.diesel").drive("Diesel car"); 570 }