About Monkey 2 › Forums › Monkey 2 Code Library › DI container, Inverse of Control
This topic contains 2 replies, has 2 voices, and was last updated by
mac767 1 year ago.
-
AuthorPosts
-
March 21, 2018 at 10:50 am #14079
Hi. I wrote IoC-container (inverse of control) that helps us incapsulate creation of new instances.
It named Di to be short, that mean dependancy inversion.
This container stores bindings of type T1,T2,.. with their realization.
You can bind interface type with its concrete realization.
The goal is to reduce creation of instances by New and use container instead.
Short example:
Monkey1234' w/o ioc-containerLocal presenter:=New Presenter( New Game,New LocalMetrica,New OutputLogger )' with itLocal presenter:=Di.Resolve<Presenter>()Yes, you must create all these Game, Metrica, Logger – but in special place, not in your business logic.
Important: Di store reference to functions, that will create our realization. If you don’t call, for example Di.Resolve then realization of ISomeModel will not be created at all.
It may be profit – if realization isn’t cost-free of resources etc.
But the main profit is – you can access needed instances in any place of your app w/o worrying about what params you should pass into constructor.
And if you need to change realization – you can change the only place where you setup container and it’ll change all of places where it used.
Ok, there is a code with 2 exaples.
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269Namespace myapp#Import "<std>"Using std..Function Main()SetupDi()' Example #1'' let's get data provider' we know nothing about its realization' all we need to know that it is IDataProvider with GetData() methodLocal provider:=Di.Resolve<IDataProvider>()' we can do some work with dataLocal data:=provider.GetData()If data And data.Length>0Local k:=1Local min:=data[0],max:=data[0]For Local i:=Eachin dataPrint k+": "+i ' print each valuemin=Min( min,i )max=Max( max,i )k+=1NextPrint "min: "+minPrint "max: "+maxEndif' Example #2'Local presenter:=Di.Resolve<Presenter>() ' we know nothing about Game, Logger, Metrica!presenter.ClickOnPlay()presenter.UseCheat( "monkey2-di-works")End#Rem monkeydoc Setup (bind) needed interfaces and it's realizations.#EndFunction SetupDi()' Example #1'' Di.Bind( Lambda:IDataProvider()' Print "create production DataProvider"' Return New DataProvider' End )Di.Bind( Lambda:IDataProvider()Print "create develop DataProvider"Return New TestDataProviderEnd )' Example #2'Di.Bind( Lambda:IMetrica()Return New LocalMetricaEnd )Di.Bind( Lambda:ILogger()Return New OutputLoggerEnd )Di.Bind( Lambda:Game()Return New GameEnd )Di.Bind( Lambda:Presenter()Return New Presenter(' we pass instances that were already binded' this call will occur when (if) we call Di.Resolve<Presenter>()Di.Resolve<Game>(),Di.Resolve<IMetrica>(),Di.Resolve<ILogger>() )End )End' ----------------------------------------' Some code for demonstration #1'Interface IDataProviderMethod GetData:Int[]()EndClass DataProvider Implements IDataProviderMethod GetData:Int[]()Local arr:=New Int[100]For Local i:=0 Until 100arr[i]=Rnd( 0,1000 )NextReturn arrEndEndClass TestDataProvider Implements IDataProviderMethod GetData:Int[]()Local arr:=New Int[20]For Local i:=0 Until 20arr[i]=Rnd( -1000,0 )NextReturn arrEndEnd' ----------------------------------------' ----------------------------------------' Some code for demonstration #2'Interface ILoggerMethod Log( msg:String )EndClass OutputLogger Implements ILoggerMethod Log( msg:String )Print "log: "+msgEndEndInterface IMetricaMethod Add( msg:String,args:String="" )EndClass LocalMetrica Implements IMetricaMethod Add( msg:String,args:String="" )Local s:=LoadString( "asset::metrica.json" )s += "~n"+msg+"~n"+args+"~n----------------------"SaveString( s,"asset::metrica.json" )EndEndClass GameMethod Play()Print "game. play"EndMethod Stop()Print "game. stop"EndEndClass PresenterMethod New( game:Game,metrica:IMetrica,logger:ILogger )_game=game_metrica=metrica_logger=loggerEndMethod ClickOnPlay()_game.Play()_metrica.Add( "ClickOnPlay" )EndMethod ClickOnStop()_game.Stop()_metrica.Add( "ClickOnStop" )EndMethod UseCheat( cheatCode:String )_logger.Log( "useCheat: "+cheatCode )EndPrivateField _game:GameField _metrica:IMetricaField _logger:ILoggerEnd' ----------------------------------------#Rem monkeydoc Container class that stores bindings of type <-> realization_of_type.Type can be interface or class type.#EndClass Di Final#Rem monkeydoc This function add new binding.You can add the only type of T function.@param bindFunc a function that provide realization of T type@param singleton if true - we want to have the only instance of T (singleton), if false - we want to instantiate new T every call of Resolve()#EndFunction Bind<T>( bindFunc:T(),singleton:Bool=True )Local type:=Typeof<T>If singletonAssert( Not BINDERS_SINGLE.Contains( type ),"Di-container : type "+type+" already exists!" )BINDERS_SINGLE[type]=bindFuncElseAssert( Not BINDERS_NEW_INST.Contains( type ),"Di-container : type "+type+" already exists!" )BINDERS_NEW_INST[type]=bindFuncEndifEnd#Rem monkeydoc This function return instance of T.If type was added as singleton - we always will get the same instance.Otherwise - we always will get new instance.#EndFunction Resolve<T>:T()Local type:=Typeof<T>Local binder:=BINDERS_NEW_INST[type]If binder<>NullReturn Cast<T()>( binder )()EndifLocal result:=SINGLETONS[type]If result=Nullbinder=BINDERS_SINGLE[type]If binderLocal result2:=Cast<T()>( binder )()Assert( result2<>Null,"Di-container: realization for "+type+" not found!" )SINGLETONS[type]=result2Return result2EndifEndifAssert( result<>Null,"Di-container: realization for "+type+" not found!" )Return Cast<T>( result )EndPrivateConst SINGLETONS:=New Map<TypeInfo,Variant>Const BINDERS_NEW_INST:=New Map<TypeInfo,Variant>Const BINDERS_SINGLE:=New Map<TypeInfo,Variant>EndAnd separated Di class:
Monkey12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364#Rem monkeydoc Container class that stores bindings of type <-> realization_of_type.Type can be interface or class type.#EndClass Di Final#Rem monkeydoc This function add new binding.You can add the only type of T function.@param bindFunc a function that provide realization of T type@param singleton if true - we want to have the only instance of T (singleton), if false - we want to instantiate new T every call of Resolve()#EndFunction Bind<T>( bindFunc:T(),singleton:Bool=True )Local type:=Typeof<T>If singletonAssert( Not BINDERS_SINGLE.Contains( type ),"Di-container : type "+type+" already exists!" )BINDERS_SINGLE[type]=bindFuncElseAssert( Not BINDERS_NEW_INST.Contains( type ),"Di-container : type "+type+" already exists!" )BINDERS_NEW_INST[type]=bindFuncEndifEnd#Rem monkeydoc This function return instance of T.If type was added as singleton - we always will get the same instance.Otherwise - we always will get new instance.#EndFunction Resolve<T>:T()Local type:=Typeof<T>Local binder:=BINDERS_NEW_INST[type]If binder<>NullReturn Cast<T()>( binder )()EndifLocal result:=SINGLETONS[type]If result=Nullbinder=BINDERS_SINGLE[type]If binderLocal result2:=Cast<T()>( binder )()Assert( result2<>Null,"Di-container: realization for "+type+" not found!" )SINGLETONS[type]=result2Return result2EndifEndifAssert( result<>Null,"Di-container: realization for "+type+" not found!" )Return Cast<T>( result )EndPrivateConst SINGLETONS:=New Map<TypeInfo,Variant>Const BINDERS_NEW_INST:=New Map<TypeInfo,Variant>Const BINDERS_SINGLE:=New Map<TypeInfo,Variant>EndMarch 22, 2018 at 7:06 am #14093I created repository for that:
https://github.com/engor/m2-di-container
Repository contains core class di.monkey2 and two demo programs.
Also I changed code above – FuncWrapper was removed.
PS: such containers are suitable for apps, and much less for games.
March 31, 2018 at 9:33 am #14186Hi nerobot,
very useful for clean code in large projects.
Thanks for sharing. -
AuthorPosts
You must be logged in to reply to this topic.