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 Besides $(D_INLINECODE set) , $(D_INLINECODE construct) , methods used in all above examples, framework offers several 13 other configuration methods, that allow greater manipulation, or less verbosity. Additional configu- 14 ration primitives are listed below: 15 16 $(OL 17 $(LI $(D_INLINECODE autowire) ) 18 $(LI $(D_INLINECODE factoryMethod) ) 19 $(LI $(D_INLINECODE callback) ) 20 $(LI $(D_INLINECODE value)) 21 $(LI $(D_INLINECODE destructor)) 22 ) 23 24 Lets start with first one from list, which is $(D_INLINECODE autowire) method. 25 From it's name we can deduce that it automates wiring of components somehow. 26 But how does it do this? Let's register a car using old $(D_INLINECODE set) and $(D_INLINECODE construct) methods: 27 ---------------- 28 register!Car("sedan.engine.default") 29 .construct(lref!Size, lref!Engine) 30 .set!"color"(lref!Color); 31 ---------------- 32 33 Autowire: 34 35 Even with more structured instantiation code present using framework, sometimes, even this 36 code is too verbose. Some components for example have a set of dependencies defined by some 37 interface, and having for each dependency to reference it using $(D_INLINECODE lref!Type) is quite cumbersome, 38 and painfull. 39 40 And here comes into play $(D_INLINECODE autowire) configuration method, which handles referencing of 41 component by type on behalf of developer that creates the configuration, like in example below: 42 43 ----------------- 44 register!Car("sedan.engine.default") // Register a car with a default engine. 45 .autowire() // Construct Car using default implementation of a size, and engine. 46 .autowire!"color"(); // Inject default implementation of a Color. 47 ----------------- 48 49 In current implementation of autowired due to limitations of current compiler’s compile time 50 introspection, $(D_INLINECODE autowire) cannot work well with overloaded methods or constructors. In such cases, 51 $(D_INLINECODE autowire) cannot know which version of method/constructor to overload, and will default to the 52 first one from the list of overloads. 53 54 FactoryMethod: 55 56 Frequently in applications that use third party code, components from that third party code, 57 are instantiated using factories or other entities from that third party code, and the client code of 58 those libraries simply are not allowed to manually create those types. To aleviate this problem, when 59 such a component is registered in a container, framework allows such components to be instantiated 60 by using third party code responsible for this task. 61 62 Supposedly, the car simulation application introduced previously, is upgraded with a car man- 63 ufacturing simulation, and new cars that are needed to be registered as components into container 64 are required to be instantiated using car manufacturing component. $(D_INLINECODE factoryMethod) method 65 instructs framework to use a third party component to instantiate registered component, using static 66 method of a factory, or by using an instance of a factory. In example usage of $(D_INLINECODE factoryMethod) 67 is shown: 68 69 ----------------- 70 register!Car("car_manufacturer.product.sedan") // Register a car with gasoline engine 71 .factoryMethod!(CarManufacturer, "manufacture")(lref!CarManufacturer, "size.sedan".lref); 72 ----------------- 73 74 Callback: 75 While just by configuring a component with simple values or references, in some cases during 76 construction of a component some custom logic must be run, that is or not related to instantiation 77 process. $(D_INLINECODE callback) configuration method can be used to pass a function or a delegate that 78 constructs or configures the component, according to some custom logic, not expressable by already 79 available configuration primitives. Example below shows how a callback can be used as a constructor or 80 as a configuration primitive. 81 82 ------------------ 83 register!Car("sedan.engine.electric") // Register a car with electric engine 84 .callback( 85 delegate (Locator!() loc) { // Let's construct our car using some custom logic that isn't possible to express with a simple construct method call. 86 Car car = new Car(loc.locate!Size, new ElectricEngine()); 87 88 return car; 89 } 90 ).callback( // Let's configure newly built car with a custom color. 91 delegate (Locator!() loc, Car car) { 92 car.color = loc.locate!Color; 93 } 94 ); 95 ------------------- 96 Though it's quite a simple primitive, it is as well powerful one. It is possible to define your own 97 custom configuration primitives just by using under the hood the $(D_INLINECODE callback) primitive. 98 99 Value: 100 The value primitive instructs container to use as basis an already constructed component. The component itself 101 can be further configured using primitives specified above. The only specific case that should be taken into accout 102 is the fact that using same value that is a reference type (a pointer, or class) in multiple components, will result into 103 having same component with multiple identities in container. The container itself won't be aware of the relationship, as in case 104 with aliasing. It is recommended that $(D_INLINECODE value) to be used only once per reference value. 105 106 ------------------- 107 register!GasolineEngine 108 .value(new GasolineEngine(15)); // Register a gasoline engine. Note: this engine is not the same gasoline engine from default implementation. 109 ------------------- 110 111 Destructor: 112 When container is shut down, the default behavior is to destroy all constructed components. The default destruction logic is to run destructor and 113 deallocate the component. Sometimes, the default behavior is not desired, or additional logic should be run. In such cases $(D_INLINECODE destructor) 114 primitive can be used to either pass a delegate, or factory method to be used to destroy the component. 115 116 ------------------- 117 register!Car("car_manufacturer.product.sedan") // Register a car with gasoline engine 118 .factoryMethod!(CarManufacturer, "manufacture")(lref!CarManufacturer, "size.sedan".lref) 119 .destructor((RCIAllocator alloc, ref Car car) { 120 write("Crushing the car. "); 121 alloc.dispose(car); 122 writeln("Done"); 123 }); 124 ------------------- 125 126 Those additional primitives are quite handy to use, with libraries that are usign factories, 127 dependencies by interface, or when some custom behavior has to be run during construction of a 128 component. Output below is the result of using those primitives, to run. 129 130 ------------------- 131 Uuh, what a nice car, Gasoline car with following specs: 132 Size: Size(200, 150, 300) 133 Color: Color(0, 0, 0) 134 Engine: app.GasolineEngine 135 Let`s turn it on 136 pururukVrooomVrrr 137 What a nice sound! We should make a test drive! 138 vrooom 139 Umm the test drive was awesome, let`s get home and turn it off. 140 vrooom 141 vrrrPrrft 142 143 Uuh, what a nice car, Manufactured car with following specs: 144 Size: Size(200, 150, 500) 145 Color: Color(0, 0, 0) 146 Engine: app.DieselEngine 147 Let`s turn it on 148 pururukVruumVrrr 149 What a nice sound! We should make a test drive! 150 vruum 151 Umm the test drive was awesome, let`s get home and turn it off. 152 vruum 153 vrrrPft 154 155 Uuh, what a nice car, Electric car with following specs: 156 Size: Size(200, 150, 300) 157 Color: Color(0, 0, 0) 158 Engine: app.ElectricEngine 159 Let`s turn it on 160 pzt 161 What a nice sound! We should make a test drive! 162 vvvvvvvvv 163 Umm the test drive was awesome, let`s get home and turn it off. 164 vvvvvvvvv 165 tzp 166 ------------------- 167 168 If you are puzzled from where is this output, take a look at the source code of 169 this example. Modify it, play with it to understand these configuration primitives. 170 171 License: 172 Boost Software License - Version 1.0 - August 17th, 2003 173 174 Permission is hereby granted, free of charge, to any person or organization 175 obtaining a copy of the software and accompanying documentation covered by 176 this license (the "Software") to use, reproduce, display, distribute, 177 execute, and transmit the Software, and to prepare derivative works of the 178 Software, and to permit third-parties to whom the Software is furnished to 179 do so, all subject to the following: 180 181 The copyright notices in the Software and this entire statement, including 182 the above license grant, this restriction and the following disclaimer, 183 must be included in all copies of the Software, in whole or in part, and 184 all derivative works of the Software, unless such copies or derivative 185 works are solely in the form of machine-executable object code generated by 186 a source language processor. 187 188 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 189 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 190 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 191 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 192 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 193 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 194 DEALINGS IN THE SOFTWARE. 195 196 Authors: 197 aermicioi 198 **/ 199 200 module configuration_primitives; 201 202 import aermicioi.aedi; 203 import std.experimental.allocator; 204 import std.stdio; 205 206 /** 207 A struct that should be managed by container. 208 **/ 209 struct Color { 210 ubyte r; 211 ubyte g; 212 ubyte b; 213 } 214 215 /** 216 Size of a car. 217 **/ 218 struct Size { 219 220 ulong width; 221 ulong height; 222 ulong length; 223 } 224 225 /** 226 Interface for engines. 227 228 An engine should implement it, in order to be installable in a car. 229 **/ 230 interface Engine { 231 232 public { 233 234 void turnOn(); 235 void run(); 236 void turnOff(); 237 } 238 } 239 240 /** 241 A concrete implementation of Engine that uses gasoline for propelling. 242 **/ 243 class GasolineEngine : Engine { 244 245 private double consume; 246 public { 247 this(double consume) { 248 this.consume = consume; 249 } 250 251 void turnOn() { 252 writeln("pururukVrooomVrrr"); 253 254 } 255 256 void run() { 257 writeln("munch ", consume, " of litres vroom"); 258 } 259 260 void turnOff() { 261 writeln("vrrrPrrft"); 262 } 263 } 264 } 265 266 /** 267 A concrete implementation of Engine that uses diesel for propelling. 268 **/ 269 class DieselEngine : Engine { 270 271 public { 272 273 void turnOn() { 274 writeln("pururukVruumVrrr"); 275 276 } 277 278 void run() { 279 writeln("vruum"); 280 } 281 282 void turnOff() { 283 writeln("vrrrPft"); 284 } 285 } 286 } 287 288 /** 289 A concrete implementation of Engine that uses electricity for propelling. 290 **/ 291 class ElectricEngine : Engine { 292 public { 293 294 void turnOn() { 295 writeln("pzt"); 296 297 } 298 299 void run() { 300 writeln("vvvvvvvvv"); 301 } 302 303 void turnOff() { 304 writeln("tzp"); 305 } 306 } 307 } 308 309 /** 310 A class representing a car. 311 **/ 312 class Car { 313 314 private { 315 Color color_; // Car color 316 Size size_; // Car size 317 Engine engine_; // Car engine 318 } 319 320 public { 321 322 /** 323 Constructor of car. 324 325 Constructs a car with a set of sizes. A car cannot of course have 326 undefined sizes, so we should provide it during construction. 327 328 Params: 329 size = size of a car. 330 **/ 331 this(Size size, Engine engine) { 332 this.size_ = size; 333 this.engine = engine; 334 } 335 336 @property { 337 338 /** 339 Set color of car 340 341 Set color of car. A car can live with undefined color (physics allow it). 342 343 Params: 344 color = color of a car. 345 346 Returns: 347 Car 348 **/ 349 Car color(Color color) @safe nothrow { 350 this.color_ = color; 351 352 return this; 353 } 354 355 Color color() @safe nothrow { 356 return this.color_; 357 } 358 359 Size size() @safe nothrow { 360 return this.size_; 361 } 362 363 /** 364 Set engine used in car. 365 366 Params: 367 engine = engine used in car to propel it. 368 369 Returns: 370 Car 371 **/ 372 Car engine(Engine engine) @safe nothrow { 373 this.engine_ = engine; 374 375 return this; 376 } 377 378 Engine engine() @safe nothrow { 379 return this.engine_; 380 } 381 } 382 383 void start() { 384 engine.turnOn(); 385 } 386 387 void run() { 388 engine.run(); 389 } 390 391 void stop() { 392 engine.turnOff(); 393 } 394 } 395 } 396 397 /** 398 A manufacturer of cars. 399 **/ 400 class CarManufacturer { 401 402 public { 403 Car manufacture(Size size) { 404 return new Car(size, new DieselEngine()); // Manufacture a car. 405 } 406 } 407 } 408 409 void drive(Car car, string name) { 410 writeln("Uuh, what a nice car, ", name," with following specs:"); 411 writeln("Size:\t", car.size()); 412 writeln("Color:\t", car.color()); 413 writeln("Engine:\t", car.engine()); 414 415 writeln("Let's turn it on"); 416 car.start(); 417 418 writeln("What a nice sound! We should make a test drive!"); 419 car.run(); 420 421 writeln("Umm the test drive was awesome, let's get home and turn it off."); 422 car.run(); 423 car.stop(); 424 } 425 426 void main() { 427 SingletonContainer container = singleton(); // Creating container that will manage a color 428 scope(exit) container.terminate(); 429 430 with (container.configure) { 431 register!Color; // Let's register a default implementation of Color 432 433 register!Color("color.green") // Register "green" color into container. 434 .set!"r"(cast(ubyte) 0) 435 .set!"g"(cast(ubyte) 255) 436 .set!"b"(cast(ubyte) 0); 437 438 register!Size // Let's register default implementation of a Size 439 .set!"width"(200UL) 440 .set!"height"(150UL) 441 .set!"length"(300UL); 442 443 register!Size("size.sedan") // Register a size of a generic "sedan" into container 444 .set!"width"(200UL) 445 .set!"height"(150UL) 446 .set!"length"(500UL); 447 448 register!(Engine, GasolineEngine) 449 .construct(10.0); // Register a gasoline engine as default implementation of an engine 450 register!GasolineEngine 451 .value(new GasolineEngine(15)); // Register a gasoline engine. Note: this engine is not the same gasoline engine from default implementation. 452 register!DieselEngine; // Register a diesel engine 453 454 register!CarManufacturer; // Let's register a car manufacturer in our container. 455 456 register!Car("sedan.engine.default") // Register a car with a default engine. 457 .autowire() // Construct Car using default implementation of a size, and engine. 458 .autowire!"color"(); // Inject default implementation of a Color. 459 460 register!Car("car_manufacturer.product.sedan") // Register a car with gasoline engine 461 .factoryMethod!(CarManufacturer, "manufacture")(lref!CarManufacturer, "size.sedan".lref) 462 .destructor((RCIAllocator alloc, ref Car car) { 463 write("Crushing the car with ", typeid(car.engine), " engine: "); 464 alloc.dispose(car); 465 writeln("Done"); 466 }); 467 468 register!Car("sedan.engine.electric") // Register a car with electric engine 469 .callback( 470 delegate (RCIAllocator allocator, Locator!() loc) { // Let's construct our car using some custom logic that isn't possible to express with a simple construct method call. 471 Car car = allocator.make!Car(loc.locate!Size, new ElectricEngine()); 472 473 return car; 474 } 475 ).callback( // Let's configure newly built car with a custom color. 476 delegate (Locator!() loc, Car car) { 477 car.color = loc.locate!Color; 478 } 479 ); 480 } 481 482 container.instantiate(); // Boot container (or prepare managed code/data). 483 484 container.locate!Car("sedan.engine.default").drive("Gasoline car"); 485 container.locate!Car("car_manufacturer.product.sedan").drive("Manufactured car"); 486 container.locate!Car("sedan.engine.electric").drive("Electric car"); 487 }