最近想项目中须要使用这个架构 所以 上网看了不少资料摸索 可是对于初学者来讲大多数的资料不是那么容易理解 并且文档也是英文的阅读起来有点吃力 因此记录一下本身阅读的过程 方便之后翻阅和跟我同样的新人学习其中也借鉴了一些前辈的资料 若有反感请联系我 立马进行修改 谢谢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