Skip to content

Commit 17a652c

Browse files
committed
Move how its work into readme.md
1 parent d7bca06 commit 17a652c

File tree

2 files changed

+136
-128
lines changed

2 files changed

+136
-128
lines changed

how-its-work.md

-125
This file was deleted.

readme.md

+136-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ The name itself designed to make My Own IoC Container become your IoC Container,
2525
2626
Check installation part to make My Own IoC Container become Your Own IoC Container.
2727

28+
## Code Status
29+
Stable, the library actually extracted from my internal project, and used in several small projects.
2830

2931
## Prerequisites
30-
First prerequisites is you need to understand how My Own IoC Container work, refer to [How Its Work](how-its-work.md), believe me its easier than you thing.
32+
First prerequisites is you need to understand how My Own IoC Container work, refer to [How Its Work](#how-its-work), believe me its easier than you think.
3133

3234
To use My Own IoC Container required you to use TypeScript with below `tsconfig.json`:
3335

@@ -41,13 +43,17 @@ To use My Own IoC Container required you to use TypeScript with below `tsconfig.
4143
}
4244
```
4345

46+
* Transpile target: ES6 minimum
47+
* Experimental decorators enabled
48+
* Emit decorator metadata enabled
49+
4450
All above configuration is required.
4551

4652
# Features
4753
My Own IoC Container support most of common IoC Container features:
4854

4955
- [x] Single file, copy - paste able, less then 15kb/300 lines of code
50-
- [x] Supported Singleton and Transient lifestyle (Transient is default)
56+
- [x] Lifestyle support: Singleton and Transient (Transient is default)
5157
- [x] Constructor injection
5258
- [x] Inject by type
5359
- [x] Inject by name for interface injection
@@ -306,6 +312,7 @@ const container = new Container();
306312
container.register(Computer)
307313
.onCreated(instance => Benalu.fromInstance(instance)
308314
.addInterception(i => {
315+
//intercept execution of "start" method
309316
if(i.memberName == "start"){
310317
console.log("Before starting computer...")
311318
i.proceed()
@@ -325,4 +332,130 @@ Computer ready
325332

326333
Above code showing that we intercept the execution of `Computer.start()` method adding console log before and after execution.
327334

328-
> Second parameter of the `onCreated` method is instance of `Kernel`
335+
> Second parameter of the `onCreated` method is instance of `Kernel`
336+
337+
# How Its Work
338+
339+
Basically all IoC Container consist of two main big part: Registration part and Resolution part. Registration part convert registered type into component model, resolution part analyze the component model dependency graph and convert component model into type instance.
340+
341+
> NOTE
342+
>
343+
> below explanation and code snippet intended to be as simple as possible to easier for you to understand. In the real implementation of My Own IoC Container is a lot more robust and extensible than that but still easy to understand.
344+
345+
## Registration
346+
347+
For example we have classes below, and register it in the container.
348+
349+
```typescript
350+
//classes
351+
interface Monitor {}
352+
class LGMonitor implements Monitor { }
353+
class PowerSupply {}
354+
class Computer {
355+
constructor(
356+
//inject by name (interface injection)
357+
@inject.name("Monitor") private monitor:Monitor
358+
//inject by type
359+
private power:PowerSupply){ }
360+
}
361+
362+
//registration
363+
container.register("Monitor").asType(LGMonitor)
364+
container.register(PowerSupply)
365+
container.register(Computer)
366+
```
367+
368+
Registration part will convert above class into a Component Models like below
369+
370+
```typescript
371+
[{
372+
kind: "Type",
373+
name: "Monitor"
374+
type: LGMonitor,
375+
lifeStyle: "Transient",
376+
dependencies: []
377+
}, {
378+
kind: "Type",
379+
//the name is auto generated, because registered by type
380+
//name will be used as a key on singleton cache
381+
name: "auto:PowerSupply"
382+
type: PowerSupply,
383+
lifeStyle: "Transient",
384+
dependencies: []
385+
}, {
386+
kind: "Type",
387+
name: "auto:Computer"
388+
type: Computer,
389+
lifeStyle: "Transient",
390+
//list of constructor parameters,
391+
//for more advanced scenario can be list of properties for property injection
392+
//note that dependencies only contain the Name or Type of the
393+
//dependent type, further we use recursion to resolve them
394+
dependencies: ["Monitor", PowerSupply]
395+
}]
396+
```
397+
398+
`kind` of component model used to differentiate how the component will be instantiated. Some IoC container have several registration kind: Register by type, register by instance, register for auto factory etc etc. Each registration kind has different resolving logic.
399+
400+
## Resolution
401+
Resolution part consist of two part: dependency graph analysis and resolution. Dependency graph analysis needed to catch issues that hard to trace like:
402+
* A type dependent to another type that is not registered in the container
403+
* A type contains circular dependency
404+
* A type registration causing [captive dependency](http://blog.ploeh.dk/2014/06/02/captive-dependency/)
405+
406+
I will not explain the dependency graph analysis part because its not important part, you can check the [Topological Sort](https://en.wikipedia.org/wiki/Topological_sorting) for basic understanding of the analysis.
407+
408+
We continue to resolution part, when your code asks for resolution like below:
409+
410+
```typescript
411+
const computer = container.resolve(Computer)
412+
```
413+
414+
The resolution part perform operation like below
415+
416+
```typescript
417+
//array of component model comes from registration
418+
let componentModels = []
419+
//object to store singleton cache
420+
let singletonCache = {}
421+
422+
function resolve(request){
423+
if(typeof request == "string")
424+
resolveModel(componentModels.filter(x => x.name == request)[0])
425+
else
426+
resolveModel(componentModels.filter(x => x.type == type)[0])
427+
}
428+
429+
function resolveModel(model){
430+
const instance = new model.type(...model.dependencies.map(x => resolve(x)))
431+
if(model.lifeStyle == "Singleton"){
432+
const cache = singletonCache[model.name]
433+
if(!cache) return singletonCache[model.name] = instance
434+
else return cache
435+
}
436+
else return instance
437+
}
438+
```
439+
440+
Above code is simplified version of resolution part, in real implementation it needs more robust and extensible implementation.
441+
442+
The most important part of above implementation is the instantiation process
443+
444+
```typescript
445+
const instance = new model.type(...model.dependencies.map(x => resolve(x)))
446+
```
447+
448+
Above code will create instance of the requested type and resolve the parameter recursively. For example if we request the `Computer` class, the component model is like be below:
449+
450+
```typescript
451+
{
452+
kind: "Type",
453+
name: "auto:Computer"
454+
type: Computer,
455+
lifeStyle: "Transient",
456+
dependencies: ["Monitor", PowerSupply]
457+
}
458+
```
459+
460+
So the instantiation process `new model.type()` is the same as `new Computer()`. then we recursively resolve the `model.dependencies` that is `"Monitor"` and `PowerSupply` then assigned them as the parameter of the `Computer` object using spread `...` operator.
461+

0 commit comments

Comments
 (0)