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 }