Unity StrangeIoc框架 (一)
最近想项目中需要使用这个架构 因此 上网看了很多资料摸索 但是对于初学者来说大多数的资料不是那么容易理解 而且文档也是英文的阅读起来有点吃力 所以记录一下自己阅读的过程 方便以后翻阅和跟我一样的新人学习其中也借鉴了一些前辈的资料 如有反感请联系我 立马进行修改 谢谢
文档坐标 http://strangeioc.github.io/strangeioc/TheBigStrangeHowTo.html
StrangeIoc 是依据控制反转和解耦原理设计的,支持依赖注入。
控制反转即Ioc(Inversion of Control) 它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所为的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了内部的容器。
依赖注入(Dependency Injection) 依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的写作对象。配置对象的工作应该由Ioc容器负责,
在使用时
Bingding(绑定)
strange的核心是绑定,我们可以将一个或多个对象与另外一个或多个对象绑定(连接)在一起,将接口与类绑定来实现接口,将事件与事件接收绑定在一起。或者绑定两个类,一个类被创建时另一个类自动创建。
strange的binding由两个必要部分和一个可选部分组成,必要部分是a key and a value key触发value,因此一个事件可以触发回调,一个类的实例化可以触发另一个类的实例化。可选部分是name,他可以区分使用相同key的两个binding 下面三种绑定方法其实都是一样的,语法不同而已
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, mediationbing
注入绑定injectionbinding主要是用来绑定该类型对象到上下文,这样使得程序中各个地方可以通过contextview访问得到该对象。这种绑定会生成对象。这种绑定是为了生成对象并且注入到指定对象中用的
commandbinding是为了将命令绑定到方法中用的
mediationbing则是为了拦截view消息,而将view注入中介mediator中,然后在view的awake方法里面生成meidtaor对象
The injection extension(注入扩展)
在绑定扩展中最接近控制反转的思想是注入
接口本身没有实现方法,只定义类中的规则
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);
具体还有几种映射就不说了 需要的可以去看看文档
The reflector extension(反射扩展)
反射列表
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();
The dispatcher extension(调度程序扩展)
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);
The command extension(命令扩展)
除了绑定事件的方法,可以将其绑定到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>();
The signal extension(消息扩展)
信号是一个调度机制,另一种选择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();
The mediation extension(调解器(中介模式))
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
The context extension(上下文扩展)
MVCSContext包含EventDispatcher(事件分发),injectionBinder(注入绑定),MediationBinder(中介绑定),CommandBinder(命令绑定)
可以重新将CommandBinder绑定到SignalCommandBinder 命令和中介依托注入,context可以为命令和中介的绑定提供关联
建立一个项目,需要重新MVCSContext或者Context,一个app也可以包换多个Contexts 这样可以使你的app更高的模块化,因此,一个app可以独立的设计为聊天模块,社交模块 最终他们会整合到一起成为一个完整的app
暂时记录到这里 剩下的 下一章补完