最近想項目中須要使用這個架構 所以 上網看了不少資料摸索 可是對於初學者來講大多數的資料不是那麼容易理解 並且文檔也是英文的閱讀起來有點吃力 因此記錄一下本身閱讀的過程 方便之後翻閱和跟我同樣的新人學習其中也借鑑了一些前輩的資料 若有反感請聯繫我 立馬進行修改 謝謝html
文檔座標 http://strangeioc.github.io/strangeioc/TheBigStrangeHowTo.htmlgit
StrangeIoc 是依據控制反轉和解耦原理設計的,支持依賴注入。github
控制反轉即Ioc(Inversion of Control) 它把傳統上由程序代碼直接操控的對象的調用權交給容器,經過容器來實現對象組件的裝配和管理。所爲的「控制反轉」概念就是對組件對象控制權的轉移,從程序代碼自己轉移到了內部的容器。api
依賴注入(Dependency Injection) 依賴注入的基本原則是:應用組件不該該負責查找資源或者其餘依賴的寫做對象。配置對象的工做應該由Ioc容器負責,安全
在使用時網絡
strange的核心是綁定,咱們能夠將一個或多個對象與另一個或多個對象綁定(鏈接)在一塊兒,將接口與類綁定來實現接口,將事件與事件接收綁定在一塊兒。或者綁定兩個類,一個類被建立時另外一個類自動建立。架構
strange的binding由兩個必要部分和一個可選部分組成,必要部分是a key and a value key觸發value,所以一個事件能夠觸發回調,一個類的實例化能夠觸發另外一個類的實例化。可選部分是name,他能夠區分使用相同key的兩個binding 下面三種綁定方法其實都是同樣的,語法不一樣而已mvc
1. Bind<IRoundLogic>().To<RoundLogic>(); 2. Bind(typeof(IRoundLogic)).To(typeof(RoundLogic)); 3. IBinding binding = Bind<IRoundLogic>(); //使用IBinding 的時候須要引用strange.framework.api; 命名空間 binding.To<RoundLogic>();
Bind<IRoundLogic>().To<RoundLogic>().ToName(「Logic」); //使用非必要部分name
綁定從層次上分爲3種: injectionbinding ,commandbinding, mediationbingapp
注入綁定injectionbinding主要是用來綁定該類型對象到上下文,這樣使得程序中各個地方能夠經過contextview訪問獲得該對象。這種綁定會生成對象。這種綁定是爲了生成對象而且注入到指定對象中用的異步
commandbinding是爲了將命令綁定到方法中用的
mediationbing則是爲了攔截view消息,而將view注入中介mediator中,而後在view的awake方法裏面生成meidtaor對象
在綁定擴展中最接近控制反轉的思想是注入
接口自己沒有實現方法,只定義類中的規則
interface ISpaceship { void input(float angle, float velocity); IWeapon weapon{get;set;} } //使用另外一個類實現這個接口,寫法以下 Class Spaceship : ISpaceship { public void input(float angle, float velocity) { //do } public IWeapon weapon{get;set;} }
若是採用上面的寫法,Spaceship類裏面不用再寫檢測輸入的功能了,只須要處理輸入就能夠了input只須要控制移動,不須要管是何種輸入方式 是手柄鍵盤或是其餘 只須要進行處理
也不須要武器的邏輯,僅僅是注入武器實例就能夠了。可是咱們須要知道武器是什麼樣的武器 不一樣的武器形成不一樣的掉血 因此這塊的邏輯是須要處理的
public interface IWeapon
{
void Attack();
}
public class PhaserGun : IWeapon
{
public void Attack(){//掉血邏輯
}
}
public class SquirtCannon : IWeapon { public void Attack(){//掉血邏輯 } }
在ISpaceship中的代碼進行一點修改
interface ISpaceship { void input(float angle, float velocity); [Inject] IWeapon weapon{get;set;} }
加上Inject標籤 這樣就能夠進行綁定了 將接口與類綁定來實現接口
[Inject]標籤實現接口,而不是實例化類
injectionBinder.Bind<IWeapon>().To<PhaserGun >();
injectionBinder.Bind<IWeapon>().To<PhaserGun >().ToStringleton();
IWeapon weapon = PhaserGun.Get();
在綁定多個的時候就須要利用 名稱映射來進行區分
injectionBinder.Bind<ISocialService>() .To<TwitterService>().ToSingleton() .ToName(ServiceTypes.PRIMARY); injectionBinder.Bind<ISocialService>() .To<TwitterService>().ToSingleton() .ToName(ServiceTypes.SECONDARY); injectionBinder.Bind<ISocialService>() .To<TwitterService>().ToSingleton() .ToName(ServiceTypes.TERTIARY);
在[Inject]標籤處 也須要進行添加名稱
[Inject (ServiceTypes.TERTIARY)] //We mapped TwitterService to TERTIARY public ISocialService socialService{get;set;}
Configuration myConfig = loadConfiguration(); injectionBinder.Bind<IConfig>().ToValue(myConfig);
具體還有幾種映射就不說了 須要的能夠去看看文檔
反射列表
List<Type> list = new List<Type> (); list.Add (typeof(Borg)); list.Add (typeof(DeathStar)); list.Add (typeof(Galactus)); list.Add (typeof(Berserker)); //count should equal 4, verifying that all four classes were reflected. int count = injectionBinder.Reflect (list);
反射全部已經經過injectionBinder映射的全部
injectionBinder.ReflectAll();
dispatcher至關於觀察者模式中的公告板,容許客戶監聽他,而且告知當前發生的事件。在strangeioc中,經過EventDispatcher方式實現,EventDispatcher綁定觸發器來觸發帶參數/不帶參數的方法 觸發器一般是String或枚舉類型(觸發器能夠理解爲key,或者事件的名稱,名稱對應着觸發的方法)
若是有返回值,他將存在IEvent,一個簡單的值對象包含與該事件相關的任何數據,你能夠寫你本身的事件知足IEvent接口,strangeioc事件叫TmEvent
若是你再使用MVCSContext 有一個全局的EventDispatcher 叫contextDispatcher 會自動注入,你能夠用來傳遞事件
有兩種基本的事你能夠去作EventDipatcher調度事件和監聽他們
dispatcher.AddListener("FIRE_MISSILE", onMissileFire);
事件會處於監聽狀態,知道FIRE_MISSILE事件被處罰,而後執行對應的onMissileFire方法
也能夠經過枚舉實現
dispatcher.AddListener(AttackEvent.FIRE_MISSILE, onMissileFire);
移除監聽
dispatcher.RemoveListener(AttackEvent.FIRE_MISSILE, onMissileFire);
更新監聽
dispatcher.UpdateListener(true, AttackEvent.FIRE_MISSILE, onMissileFire);
調用的方法能夠有一個參數或者沒有,這取決於你關心的事件效率
private void onMissileFire() { //this works... } private void onMissileFire(IEvent evt) { //...and so does this. Vector3 direction = evt.data as Vector3; }
調度事件
dispatcher.Dispatch(AttackEvent.FIRE_MISSILE);
這種形式的調度將生成一個新的TmEvent 調用任何監聽對象,可是由於你沒有提供數據,數據字段的TmEvent固然會是零。 你也能夠調度和提供數據:
Vector3 orientation = gameObject.transform.localRotation.eulerAngles; dispatcher.Dispatch(AttackEvent.FIRE_MISSILE, orientation);
能夠本身建立TmEvent調度
Vector3 orientation = gameObject.transform.localRotation.eulerAngles;
TmEvent evt = new TmEvent(AttackEvent.FIRE_MISSILE, dispatcher, this.orientation); dispatcher.Dispatch(evt);
除了綁定事件的方法,能夠將其綁定到Commands。命令是控制器結構MVC的指揮者在strangeioc的MVCSContext中CommandBinder監聽每個dispatcher的調度(固然你能夠改變這個若是你想在本身的上下文)。信號,下面描述,也能夠綁定到命令。當一個事件或信號被調度,
using strange.extensions.command.impl; using com.example.spacebattle.utils; namespace com.example.spacebattle.controller { class StartGameCommand : EventCommand { [Inject] public ITimer gameTimer{get;set;} override public void Execute() { gameTimer.start(); dispatcher.dispatch(GameEvent.STARTED); } } }
但異步命令, 像網絡請求 能夠這樣作 Retain()
and Release()
using strange.extensions.command.impl; using com.example.spacebattle.service; namespace com.example.spacebattle.controller { class PostScoreCommand : EventCommand { [Inject] IServer gameServer{get;set;} override public void Execute() { Retain(); int score = (int)evt.data; gameServer.dispatcher.AddListener(ServerEvent.SUCCESS, onSuccess); gameServer.dispatcher.AddListener(ServerEvent.FAILURE, onFailure); gameServer.send(score); } private void onSuccess() { gameServer.dispatcher.RemoveListener(ServerEvent.SUCCESS, onSuccess); gameServer.dispatcher.RemoveListener(ServerEvent.FAILURE, onFailure); //...do something to report success... Release(); } private void onFailure(object payload) { gameServer.dispatcher.RemoveListener(ServerEvent.SUCCESS, onSuccess); gameServer.dispatcher.RemoveListener( ServerEvent.FAILURE, onFailure); //...do something to report failure... Release(); } } }
若是使用完不進行Release()可能會致使內存泄露
映射命令
commandBinder.Bind(ServerEvent.POST_SCORE).To<PostScoreCommand>();
您能夠將多個命令綁定到單個事件若是你喜歡
commandBinder.Bind(GameEvent.HIT).To<DestroyEnemyCommand>().To<UpdateScoreCommand>();
解除命令綁定Unbind
commandBinder.Unbind(ServerEvent.POST_SCORE);
一次性的指令
commandBinder.Bind(GameEvent.HIT).To<DestroyEnemyCommand>().Once();
按順序執行綁定 InSequence 會一直執行到最後的命令 或者其中一個命令失敗。 命令能夠在任什麼時候候調用Fail() 這會打破這個序列 這能夠用於創建一個鏈相關的事件 爲構建有序的動畫,或制定一個守衛,以肯定是否應該執行一個命令。
commandBinder.Bind(GameEvent.HIT).InSequence() .To<CheckLevelClearedCommand>() .To<EndLevelCommand>() .To<GameOverCommand>();
信號是一個調度機制,另外一種選擇EventDispatcher 相比於EventDispatcher 信號有兩個優勢 1. 分發結果再也不建立實例,所以也不須要GC回收更多的辣雞 2. 更安全 當消息與回調不匹配時會斷開執行,官網也推薦使用Singal來兼容後續版本
建立兩個信號,每個都有一個參數
Signal<int> signalDispatchesInt = new Signal<int>(); Signal<string> signalDispatchesString = new Signal<string>();
signalDispatchesInt.AddListener(callbackInt); //Add a callback with an int parameter
signalDispatchesString.AddListener(callbackString); //Add a callback with a string parameter
signalDispatchesInt.Dispatch(42); //dispatch an int
signalDispathcesString.Dispatch("Ender wiggin"); //dispatch a string
void callbackInt(int value){
//Do something with this int
}
void callbackString(string value){
//Do something with this string
}
消息最多可使用四個參數
Signal<T, U, V, W> signal = new Signal<T, U, V, W>();
子類能夠編寫本身的信號
using System; using UnityEngine; using strange.extensions.signal.impl; namespace mynamespace { //We're typing this Signal's payloads to MonoBehaviour and int public class ShipDestroyedSignal : Signal<MonoBehaviour, int> { } }
protected override void addCoreComponents() { base.addCoreComponents(); injectionBinder.Unbind<ICommandBinder>(); injectionBinder.Bind<ICommandBinder>().To<SignalCommandBinder>().ToSingleton(); }
這告訴strangeioc 咱們作了默認CommandBinder SignalCommandBinder取而代之。 因此是信號觸發命令 而不是事件 。 strangeioc 只支持 事件或者信號中的一個映射命令,而不是兩個都是。
信號綁定 依舊使用commandBinder
commandBinder.Bind<SomeSignal>().To<SomeCommand>();
映射一個信號到命令 會自動建立一個injection映射 你能夠經過[Inject]標籤 檢索
[Inject] public ShipDestroyedSignal shipDestroyedSignal{get; set;}
commandBinder.Bind<ShipDestroyedSignal>().To<ShipDestroyedCommand>();
在ShipMediator,咱們注入信號,而後調度
[Inject] public ShipDestroyedSignal shipDestroyedSignal{get; set;} private int basePointValue; //imagining that the Mediator holds a value for this ship //Something happened that resulted in destruction private void OnShipDestroyed() { shipDestroyedSignal.Dispatch(view, basePointValue); }
派遣一個信號經過SignalCommandBinder映射結果的實例化ShipDestroyedCommand:
using System; using strange.extensions.command.impl; using UnityEngine; namespace mynamespace { //Note how we extend Command, not EventCommand public class ShipDestroyedCommand : Command { [Inject] public MonoBehaviour view{ get; set;} [Inject] public int basePointValue{ get; set;} public override void Execute () { //Do unspeakable things to the destroyed ship } } }
如您所見,映射的方法很是相似於信號命令的方法使用事件
兩個重要問題:第一,而信號支持多個相同類型的參數,注射。 所以不可能對一個信號與相同類型的兩個參數映射到一個命令
//This works Signal<int, int> twoIntSignal = new Signal<int, int>(); twoIntSignal.AddListener(twoIntCallback); //This fails Signal<int, int> twoIntSignal = new Signal<int, int>(); commandBinder.Bind(twoIntSignal).To<SomeCommand>();
override public void Launch() { base.Launch(); //Make sure you've mapped this to a StartCommand! StartSignal startSignal= (StartSignal)injectionBinder.GetInstance<StartSignal>(); startSignal.Dispatch(); }
映射沒有命令的信號
一個信號映射到一個命令會自動建立一個映射,您能夠檢索經過注入到其餘地方 可是若是你想注入信號沒有綁定到一個命令 使用injectionBinder只需將它映射
injectionBinder.Bind<ShipDestroyedSignal>().ToSingleton();
MediationContext是惟一一個專爲unity設計的部分,由於mediation關心的是對view(GameObject)的操做。因爲view部分天生的不肯定性,咱們推薦view由兩種不一樣的monobehavior組成:View and Mediator
view就是mvc中的v,一個view就是一個你能夠編寫的邏輯,控制可見部分的monobehavior 這個類能夠附加(拖拽)到unity編輯器來管理GameObject 可是不建議將mvc中的models和controller邏輯卸載view中
Mediator類的職責是執行view和整個應用的運行。他會獲取整個app中分發和接收時間和消息。可是由於mediator的設計,建議使用命令模式(command)來作這部分功能
using Strange.extensions.mediation.impl; using com.example.spacebattle.events; using com.example.spacebattle.model; namespace com.example.spacebattle.view { class DashboardMediator : EventMediator { [Inject] public DashboardView view{get;set;} override public void OnRegister() { view.init(); dispatcher.AddListener (ServiceEvent.FULFILL_ONLINE_PLAYERS, onPlayers); dispatcher.Dispatch (ServiceEvent.REQUEST_ONLINE_PLAYERS); } override public void OnRemove() { dispatcher.RemoveListener (ServiceEvent.FULFILL_ONLINE_PLAYERS, onPlayers); } private void onPlayers(IEvent evt) { IPlayers[] playerList = evt.data as IPlayers[]; view.updatePlayerCount(playerList.Length); } } }
1.DashboardView注入可使 Mediator 知道具體的view
2.注入完成後OnRegister()方法會當即執行,能夠用這個方法來作初始化 像上面作的那樣 初始化 而後作重要的數據請求
3.contextDispatcher能夠擴展任何的EventMediator
4.OnRemove()清理時使用,當一個view銷燬前被調用,移除時記得刪除你的監聽
5.例子中的view暴露兩個接口init()和updatePlayerCount(float value),可是程序在設計時 你須要更多,可是原則是相同的 限制中介除了薄任務之間的傳遞信息的查看和其餘應用程序
綁定一個界面到Mediator
mediationBinder.Bind<DashboardView>().To<DashboardMediator>();
值得注意的幾點
1.不是全部的MonoBehaviour被限制爲一個View
2.中介者綁定是實例對實例的,也就是說一個view對應一個mediator,若是有不少view,也就會有不少的mediator
MVCSContext包含EventDispatcher(事件分發),injectionBinder(注入綁定),MediationBinder(中介綁定),CommandBinder(命令綁定)
能夠從新將CommandBinder綁定到SignalCommandBinder 命令和中介依託注入,context能夠爲命令和中介的綁定提供關聯
創建一個項目,須要從新MVCSContext或者Context,一個app也能夠包換多個Contexts 這樣可使你的app更高的模塊化,所以,一個app能夠獨立的設計爲聊天模塊,社交模塊 最終他們會整合到一塊兒成爲一個完整的app