martedì, settembre 04, 2007

KentaIoC - A js IOC library - english version


Thanks to Rey9999 for this marvelous translation.
original document here: Italian version



I've been lately using an IOC framework at my workplace and I grasp the potential of such an approach right from the start, since it lets you keep your classes conceptually separated. 

To achieve this, we follow a single concept: keep the logic that binds the classes together outside of those classes.

Often, in traditional programming, an object "A" inside a method configures another object "B", before calling some other method of the latter.

With Inversion of Control instead, object "A" will never need to configure object "B", instead it will just call it directly.

Probably I was not clear enough, so I invite whoever wishes to understand better this topic to study it deeply elsewhere - I will now pragmatically focus on writing some code which will let us use IOC in JavaScript.


How do we set up IOC?

Usually we have a class "A", keeping inside a member "B" extending a known interface: this way, even if "A" does not know the actual implementation of "B", it can communicate with it, as between "A" and "B" there's a kind of "contract" defined by the interface.

Sure, but in Javascript there are no interfaces, so programming through interfaces is impossible!


Of course, this is the first problem that I will write about.
Let me first point out that JS is a dynamic language and it has no Strict Typing (even if Andr3a will surely argue).

Technically, it could be possible to simulate interfaces using JS - but this would violate the KISS principle, so I will use an alternative approach, that whilst being completely unrelated to interfaces, will let us work around this limitation.

This approach is called Duck typing, that can be summarized as:
<< If our class moves like a duck and has wings like a duck, then for us it's a duck (even if it is actually a pheasant) >>.

Another concept must be explained along with IOC: Dependency Injection,
which roughly says that an object "C" which knows neither objects "A" or "B" will be in charge of putting "the right B" inside "A".

"C" is also our container.


What exactly does a container do?
A container wraps a table uniquely identifying an object instance (usually components: tiny objects which do a single thing - but do it RIGHT), associating an unique identifier to it.

Yeah, right, cool stuff, but isn't it right about time to show some code?

Here's how we can easily manage a container:
var c={};
    //configuration of c
    c.B = { getMessage: function(){ return "test Container" } }
    
    //A doesn't know exactly B it only know that B have getMessage(duck typing approach)
    var A = {
        B: c.B, //A doesn't have the implementation of B,it rely on C to pass the correct one.
        test: function(){ alert(this.B.getMessage()) }
    }
    
    A.test();

Excuse me, but from what I understood so far, isn't "C" just a hashtable?

Well, no. Actually I'm introducing a concept at a time - in our implementation "C" is an hashtable, but to get a correct management of the IOC, this is not enough, a container must do more than that.

Depending on the configuration, a container must be able to instance the correct object (Factory). Here's where injection comes in: our container must be able to inject the object inside another object.

Each framework use different methodologies to inject dependencies. 

The two most common ways, which you will probably find in every framework, are injection by constructor and injection by setter. To keep things simple, my script depends from the extend script by Andrea Giammarchi, so make sure to include before your script.

var A ={
    test: function(){ alert(this.B.getMessage()) } //duck typing 
};

var container = {
    C : {},
     register:function (id,component){
       this.C[id]=component;
     },
     find: function (id){
       return this.C[id];
     },
     bySetter: function(){
       var id=arguments[0];
     
       var Obj = this.find(id);
       
       for(var i=1,max=arguments.length;i<max;i++  ){
         Obj = Obj.extend(arguments[i]);
     
       }

       return Obj;
     
    },
    byConstructor : function(){
  
      var id = arguments[0];
      var pars = new Array();
      for(var i=1,max=arguments.length;i<max;i++  )
       pars.push(arguments[i]);
  

      var Obj=this.find(id);
      return Obj.apply(Obj,pars);
 

   }

}

/*Start setter configuration*/
container.register('AS', A);

container.register('BS', {getMessage: function(){return 'Hello setter injection!'}});
container.register('BS2', {getMessage: function(){return 'Hello setter injection #2!'}});



container.bySetter('AS', {'B': container.find('BS')} ).test();
container.bySetter('AS', {'B': container.find('BS2')} ).test();


/*Constructor injection example*/
function AC(B){

  return {
    test:function(){alert(B.getMessage())}
  }
}

container.register('BC', { getMessage: function(){return 'Hello constructor injection '} });
container.register('AC', AC);


container.byConstructor('AC', container.find('BC')).test();



The reusable part of the script above is the container I wrote. It lets us register the components using register(id, component), as well as searching already registered components with find(id).
Last but not least, it lets you injecting by constructor with byConstructor(id, parameters) and injecting dependencies through a setter with bySetter(id, setterObject)


Finally, thanks to Andrea Giammarchi, you can find the code here: http://www.devpro.it/code/164.html
Posta un commento