概念
StrangeIoc 是依据控制反转和解耦原理设计的,支持依赖注入。
控制反转即Ioc(Inversion of Control) 它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所为的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了内部的容器。
依赖注入(Dependency Injection) 依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的写作对象。配置对象的工作应该由Ioc容器负责。
先用一个小例子解释一下什么是依赖注入:
如果设计一个玩家攻击敌人的游戏,玩家可以装备1.手枪(攻击力10),2.步枪(攻击力20)。最简单的代码实现如下:
如上代码,假如项目中多一个武器(机枪),那么就需要重写Player类,还要对(攻击逻辑)fi..else进行修改,所以设计不合理。
重新设计代码,可以用以下方式实现:
如果再增加机枪,只需要增加一个机枪类继承IGun接口就可以在Player中:
Public IGun gun=new JiQiang();
上面这种方式简单实现了依赖注入,使得大家都依赖接口编程,如果大家想详细理解依赖注入的思想,可以多找些类似的资料,这篇文章偏重StrangeIoc的框架讲解,这里依赖注入的内容不详细多说!
流程
strangeioc框架图描述一个最基本的业务流程:
1.玩家点击了一下UI
2.mediator得到通知(回调),因为它绑定了UI事件
3.mediator触发了一次command执行指令,因为mediator和command进行了绑定
4.command执行自己的execute方法
5.请求Service从一个文件中读取数据
6.Service读取数据完成后,触发事件通知Command执行execute函数。
7.command会把加载后的数据送给Model
8.Model检测到自己更新数据后,通知mediator说有新数据来了
9.Mediator就会把数据送给UI,显示到界面上。
原则:
View应该只负责显示和输入,当输入事件发生时,view应当通知mediator,绝不能让View直接通知给Model和Service,View只会分发事件,例如某个按钮按下了,至于对这个按钮的响应,应该交给Mediator来做,例如view告知说帮助按钮按下了,mediator知道后,调用具体的command再去执行具体的任务。Model和Service是被Command来使用的,而不会去监听view的事件。
下面进入strangeioc框架的详细讲解:
框架介绍
1.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 ,CommandBinder, MediationBinder
Injectionbinding:主要是用来绑定该类型对象到上下文,这样使得程序中各个地方可以通过contextview访问得到该对象。这种绑定会生成对象。这种绑定是为了生成对象并且注入到指定对象中用的。
commandbinding:是为了将命令绑定到方法中用的。
MediationBinder:则是为了拦截view消息,而将view注入中介mediator中,然后在view的awake方法里面生成meidtaor对象。
2.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中的代码进行一点修改,加上Inject标签 这样就可以进行绑定了 将接口与类绑定来实现接口,代码如下:
interface ISpaceship
{
void input(float angle, float velocity);
[Inject]
IWeapon weapon{get;set;}
}
下面[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)]
public ISocialService socialService{get;set;}
值的映射
Configuration myConfig = loadConfiguration();
injectionBinder.Bind<IConfig>().ToValue(myConfig);
具体还有几种映射就不多说,可以查看具体官方文档。
3.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));
int count = injectionBinder.Reflect (list);
反射所有已经通过injectionBinder映射的所有
injectionBinder.ReflectAll();
4.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);
5.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(),如果使用完不进行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();
}
}
}
-
映射命令
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>();
6.The signal extension(消息扩展)
信号是一个调度机制,另一种选择EventDispatcher 相比于EventDispatcher 信号有两个优点 :
- 分发结果不再创建实例,因此也不需要GC回收更多的垃圾。
- 更安全 当消息与回调不匹配时会断开执行,官网也推荐使用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
}
}
}
如您所见,映射的方法非常类似于信号命令的方法使用事件。
两个重要问题:第一,而信号支持多个相同类型的参数,注射。 因此不可能对一个信号与相同类型的两个参数映射到一个命令
//正确用法
Signal<int, int> twoIntSignal = new Signal<int, int>();
twoIntSignal.AddListener(twoIntCallback);
//错误用法
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();
7.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
8.The context extension(上下文扩展)
MVCSContext包含EventDispatcher(事件分发),injectionBinder(注入绑定),MediationBinder(中介绑定),CommandBinder(命令绑定)
可以重新将CommandBinder绑定到SignalCommandBinder 命令和中介依托注入,context可以为命令和中介的绑定提供关联
建立一个项目,需要重新MVCSContext或者Context,一个app也可以包换多个Contexts 这样可以使你的app更高的模块化,因此,一个app可以独立的设计为聊天模块,社交模块 最终他们会整合到一起成为一个完整的app。
项目(demo)详解
MVCSContex :the big picture
1.应用程序的入口是一个类成为ContextView,这是一个Monobehavior实例化MVCSContext。
2.用MVCSContext来执行各种绑定。
3.派发器是一个通信总线,允许你再程序发送消息,在mvcscontext中他们发送的是TimEvents, 或者你可以按照上面的步骤重写Context 来使用Signals。
4.命令类由TimeEvents或信号触发,然后他会执行一些app逻辑。
5.模型存储状态。
6.services与app意外的部分通信(比如接入的faebook)。
7.界面脚本附加到物体上 : 玩家与游戏的交互。
8.Mediators(中介)也是monobehavior 但是他也可以将view部分和其他部分隔离开来。
一个ContextView开始
ContextView 是一个Monobehaviour 用来实例你的Context(上下文) MyFirstProjectRoot 是ContextView的子类, 这里是应用程序的开始。
using System;
using UnityEngine;
using strange.extensions.context.impl;
using strange.extensions.context.api;
namespace strange.examples.myfirstproject
{
public class MyFirstProjectRoot : ContextView
{
void Awake()
{
//Instantiate the context, passing it this instance.
context = new MyFirstContext(this,ContextStartupFlags.MANUAL_MAPPING);
context.Start();
}
}
}
这里要使用 strange.extensions.context.impl 和 using strange.extensions.context.api 命名空间。
ContextView定义了一个属性称为上下文当然是指我们上下文。我们只需要定义它是什么我们写一个叫MyFirstContext的脚本。this指的是MyFirstProjectRoot,他告诉Context 哪个GameObject被认为是ContextView。ContextStartupFlags.MANUAL_MAPPING表明一旦我们开始一切将会继续。 调用context.Start()让它付诸行动 。 如果不调用Start则不会继续进行。
- ContextStartupFlags.AUTOMATIC : 上下文将自动映射绑定和启动(默认的)。
- ContextStartupFlags.MANUAL_MAPPING : 上线文会启动,然后在核心绑定后,在实例化或任何自定义绑定之前 将停止映射,必须调用Start()才可继续进行。
- ContextStartupFlags.MANUAL_LAUNCH : 上线文会启动,然后在核心绑定后 , 在调用ContextEvent.START 或者类似的信号前停止。必须使用Launch()继续。
The Context binds(上下文绑定)
Context(上下文)是所有绑定发生的地方,如果没有绑定,Strange应用只是一堆断开连接的部分。Context是为混乱带来秩序的胶水。从我们扩展MVCSContext,我们得到了一大堆的核心绑定,MVCSContext是为了给我们所有我们需要干净的结构 一个控制反转风格的应用:一个注射(injector)、命令总线、模型和服务支持,和中介界面。
using System;
using UnityEngine;
using strange.extensions.context.api;
using strange.extensions.context.impl;
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.dispatcher.eventdispatcher.impl;
namespace strange.examples.myfirstproject
{
public class MyFirstContext : MVCSContext
{
public MyFirstContext (MonoBehaviour view) : base(view)
{
}
public MyFirstContext (MonoBehaviour view, ContextStartupFlags flags) : base(view, flags)
{
}
protected override void mapBindings()
{
injectionBinder.Bind<IExampleModel>()
.To<ExampleModel>()
.ToSingleton();
injectionBinder.Bind<IExampleService>()
.To<ExampleService>()
.ToSingleton();
mediationBinder.Bind<ExampleView>()
.To<ExampleMediator>();
commandBinder.Bind(ExampleEvent.REQUEST_WEB_SERVICE)
.To<CallWebServiceCommand>();
commandBinder.Bind(ContextEvent.START)
.To<StartCommand>().Once ();
}
}
}
像你看到的那样,我们扩展了MVCSContext,这意味着我们继承其所有映射(探索它类的深度 ,你会发现它的有趣)。我们已经有一个injectionBinder和commandBinder和dispatcher调度员。注意,调度程序可以在整个应用程序,和CommandBinder耦合,所以任何事件派遣可以触发回调也触发命令commands和序列sequences。
这里的映射是完全符合你的期待如果你读到的各种组件注入,我们映射一个模型和一个服务都是单例。我们将只有一个视图(ExampleView)在这个例子中,我们将它绑定到一个中介(ExampleMediator)。最后,我们映射两个命令。这两个比较重要的是StartCommand绑定到一个特殊的事件:ContextEvent.START.这是事件触发启动你的应用。你需要绑定一些命令或者队列到它身上想init()为进入你的应用程序。我们绑定了.Once(),一个特殊的方法,在一次结束时被解开Unbinds。
注意这里有一个postBindings()方法。这是一个十分有用的地方放一些你需要在绑定之后运行的代码。但是他运行在Launch()之后,MVCSContext用这个方法去处理任何Views界面哪一个在寄存器中更早(在mapBindings之后被调用)。另一个明显的和有用的情况 在postBindings()中调用DontDestroyOnLoad(ContextView)。在你加载一个新的场景时用来保留ContextView(and the Context)。
A Command fires(一个命令被触发)
ContextEvent.START 被处罚,因为它被绑上了StartCommand, 一个新的StartCommand实例将被实例化出来并且执行。
using System;
using UnityEngine;
using strange.extensions.context.api;
using strange.extensions.command.impl;
using strange.extensions.dispatcher.eventdispatcher.impl;
namespace strange.examples.myfirstproject
{
public class StartCommand : EventCommand
{
[Inject(ContextKeys.CONTEXT_VIEW)]
public GameObject contextView{get;set;}
public override void Execute()
{
GameObject go = new GameObject();
go.name = "ExampleView";
go.AddComponent<ExampleView>();
go.transform.parent = contextView.transform;
}
}
}
StartCommand 扩展 EventCommand 意味着这是固定的命令CommandBinder可以处理, 他继承的所有东西都来自command 和 EventCommand。特别是,继承EventCommand意味着你得到一个IEvent注入,并且你可以访问dispatcher。
如果你只是扩展命令,您不会有自动访问这些对象,但是你依旧可以手动注入他们。
[Inject(ContextKeys.CONTEXT_DISPATCHER)]
IEventDispatcher dispatcher{get;set;}
[Inject]
IEvent evt{get;set;}
注意所使用的两种不同类型的注入。IEventDispatcher和GameObject 都是用名字创建多个实例。这是因为我们想引用这些对象的非常具体的版本。我们不希望是任意一个GameObject。我们需要一个标记像ContextView。我们也不接受任何旧IEventDispatcher。唯一一个将在上下文间通信,他标志为ContextKeys.CONTEXT_DISPATCHER。另一方面,Ievent是一个简单的映射用于这个特殊的命令(技术上他映射到一个value),所以没有必要的名字。
依赖我们将使用在当前场景是ContextView,他们添加子视图到它。
Execute()方法通过CommandBinder自动触发。大多数情况下 , 执行的顺序是这样的:
1.实例化Command命令绑定到Ievent.type。
2.注入依赖关系,包括Ievent本身。
3.调用Excute()。
4.删除Command命令。
命令不需要立即清理干净,但是我们将会得一点。如果你查看了Execute()里面的代码,你将会发现他是纯粹的Unity。创建一个GameObject,附上MonoBehaviour,然后设置它的父亲为ContextView。我们使用的是具体的MonoBehaviour(代码),然而,恰好是一个Strange IView,自从我们在context中映射这个界面。
mediationBinder.Bind<ExampleView>().To<ExampleMediator>();
这个界面是自动调度的,这意味着一个新的ExampleMediator刚刚创建!
A View is mediated(一个界面被调度)
如果你花费了一些时间为Unity编写代码,你创建一个界面,你需要调用Monobehavior,但是重点在于那个界面没有在屏幕上显示的东西。我不打算花时间执行ExampleView代码。你可以看下示例文件,如果怕你已经知道C#和Unity你不需要他。我只想引起两位的注意力。首先:
public class ExampleView : View
通过扩展View,你将会得到连接每个View到Context的代码。使用Strange 你不再需要扩展View或者重写里面的方法。但是如果你不扩展View,你依旧需要实现IView 接口。这需要确保你MonoBehaviour上下文可以操作。
第二项指出:
[Inject]
public IEventDispatcher dispatcher{get; set;}
注意 我们注入IEventDispatcher。但是跟StartCommand不是同一个调度。仔细看看代码第一个写在EventCommand(我上面显示)是这样的:
[Inject(ContextKeys.CONTEXT_DISPATCHER)]
public IEventDispatcher dispatcher{get; set;}
通过命名注入,指定的命令使用常见的上下文调度员。这个界面不应该注入dispatcher。中介的目的是隔离应用程序的视图 反之亦然Strange允许注入View。但这功能最好的时候严格限制,注入本地调度员与中介沟通很好。所以注入配置/布局文件(这是有用的,如果你发布到多个平台)。但如果你听我的劝告,不要注入入一个模型或服务或其他池外扩展的视图以及中介。
告诉你正确的方法:对于大多数开发人员来说,最难的是掌握整个框架的概念。一个视图应该只显示和输入。当某些输入发生,视图应该通知媒体。中介Mediator(允许注入上下文调度员)抽象的观点,关注与应用程序的其余部分。这个保护应用程序的视图代码,这通常和保护你的界面是混乱的,相反的情况是如此。
注意,基本视图类使用标准MonoBehaviour处理程序 Awake()
, Start(), and OnDestroy()。如果你重写这些处理程序,确保你调用了base.Awake()等。这样Strange才能正常运行。
观察调度者
using System;
using UnityEngine;
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.mediation.impl;
namespace strange.examples.myfirstproject
{
public class ExampleMediator : EventMediator
{
[Inject]
public ExampleView view{ get; set;}
public override void OnRegister()
{
view.dispatcher.AddListener
(ExampleView.CLICK_EVENT, onViewClicked);
dispatcher.AddListener
(ExampleEvent.SCORE_CHANGE, onScoreChange);
view.init ();
}
public override void OnRemove()
{
view.dispatcher.RemoveListener
(ExampleView.CLICK_EVENT, onViewClicked);
dispatcher.RemoveListener
(ExampleEvent.SCORE_CHANGE, onScoreChange);
Debug.Log("Mediator OnRemove");
}
private void onViewClicked()
{
Debug.Log("View click detected");
dispatcher.Dispatch(ExampleEvent.REQUEST_WEB_SERVICE,
"http://www.thirdmotion.com/");
}
private void onScoreChange(IEvent evt)
{
string score = (string)evt.data;
view.updateScore(score);
}
}
}
在最上方 我们注入了ExampleView。这是调度者Mediator如何知道调度那个界面。介质可以知道很多关于他们的界面。中介通常被认为是“废品(信口开河的)代码”,因为它是非常特殊的细节视图和应用程序。当然这个中介可以知道视图有一个调度者和这个调度这个程序的事件成为ExampleView.CLICK_EVENT。通过监听这个事件,中介建立了一个处理程序(onViewClicked())告诉其余的应用这个点击意味着什么。视图不应该发送REQUEST_WEB_SERVICE事件。界面只是界面。它应该像这样派发事件HELP_BUTTON_CLICKED, COLLISION, SWIPE_RIGHT。这应该是Mediator中介者的工作,映射这些事件到应用程序的其余有意义的部分。如REQUEST_HELP MISSILE_ENEMY_COLLISION PLAYER_RELOAD.他后面的事件映射到命令,这些命令会调用帮助系统,计算分数增加(增加得分模型)或确定是否允许玩家重新加载。
OnRegister()和OnRemove()方法像Mediator调度者的构造与析构函数。OnRegister()在注入后发生。所以我经常用它来设置监听和调用Init()方法实例界面、OnRemove()发生在Monobehavior的OnDestroy()被调用之后。它触发的时候你可以用来清理监听。确定你移除了你的监听,否则会产生不正确的垃圾回收。
最后注意 通过扩展EventMediator我们有公共的dispatcher。 调度者在总线监听SCORE_CHANGE事件。
Another Command fires(其他命令触发)
让我们看回Context 这行有我们忽略的问题:
commandBinder.Bind(ExampleEvent.REQUEST_WEB_SERVICE).To<CallWebServiceCommand>();
这里的意思是任何时候公共的总线收到这个事件,它会启动CallWebServiceCommand。但是他吸引你的注意力是因为通过不同的方法使用命令。
using System;
using System.Collections;
using UnityEngine;
using strange.extensions.context.api;
using strange.extensions.command.impl;
using strange.extensions.dispatcher.eventdispatcher.api;
namespace strange.examples.myfirstproject
{
public class CallWebServiceCommand : EventCommand
{
[Inject]
public IExampleModel model{get;set;}
[Inject]
public IExampleService service{get;set;}
public override void Execute()
{
Retain ();
service.dispatcher.AddListener
(ExampleEvent.FULFILL_SERVICE_REQUEST, onComplete);
string url = evt.data as string
service.Request(url);
}
private void onComplete(IEvent result)
{
service.dispatcher.RemoveListener
(ExampleEvent.FULFILL_SERVICE_REQUEST, onComplete);
model.data = result.data as string;
dispatcher.Dispatch(ExampleEvent.SCORE_CHANGE, evt.data);
Release ();
}
}
}
我们监听service,调用一个方法。我们使用事件中有效data数据来触发mediator调度者service.Request(the url)。当service结束,他派发。触发onComplate()。我们取消监听映射,设置一个值的模型,派发SCORE_CHANGE那个哪个调度者收到就做相应的处理。
但如果你一直密切关注你会记得,我之前提到过,命令后立即清理干净在Execute()完成时。所以为什么不是这个命令不会被垃圾收集。答案是最顶端调用Retain()方法保留。Retain()标志着这个命令为免除清理。这将会保持知道调用了Release()之后。显然,这意味着调用了Release()是非常重要的,否则会造成运行中的内存泄露风险。
不要允许模型和服务监听事件。 利用他们的调度者来监听事件。
Mapping Across Contexts(穿过Contexts的映射)
一般来说你要遵守上下文边界。毕竟,它的边界是有原因的 :它允许应用程序的部分功能的隔离,使程序变得更加模块化。但有时有一些对象,也许是一个模型、一个服务、或者一个信号需要需要跨多个上下文访问。
injectionBinder.Bind<IStarship>().To<HeartOfGold>().ToSingleton().CrossContext();
添加CrossContext()信号绑定需要实例化穿过context边界。它将提供给所有孩子contexts。注意,也可以覆盖一个CrossContext绑定。如果你隐射局部的key,本地的绑定将会覆盖CrossContext的那一个。
以上就是所有总结的内容!!
参考
本人才疏学浅,如有错误欢迎指正!
网友评论