【Unity】mvcs中StrangeIoc框架的概念和使用方法汇总(详尽版)
2017-02-21 17:40
525 查看
最近想项目中需要使用这个架构 因此 上网看了很多资料摸索 但是对于初学者来说大多数的资料不是那么容易理解 而且文档也是英文的阅读起来有点吃力 所以记录一下自己阅读的过程 方便以后翻阅和跟我一样的新人学习其中也借鉴了一些前辈的资料 如有反感请联系我 立马进行修改 谢谢
文档坐标 http://strangeioc.github.io/strangeioc/TheBigStrangeHowTo.html
StrangeIoc 是依据控制反转和解耦原理设计的,支持依赖注入。
控制反转即Ioc(Inversion of Control) 它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所为的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了内部的容器。
依赖注入(Dependency Injection) 依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的写作对象。配置对象的工作应该由Ioc容器负责,
在使用时
strange的核心是绑定,我们可以将一个或多个对象与另外一个或多个对象绑定(连接)在一起,将接口与类绑定来实现接口,将事件与事件接收绑定在一起。或者绑定两个类,一个类被创建时另一个类自动创建。
strange的binding由两个必要部分和一个可选部分组成,必要部分是a key and a value key触发value,因此一个事件可以触发回调,一个类的实例化可以触发另一个类的实例化。可选部分是name,他可以区分使用相同key的两个binding 下面三种绑定方法其实都是一样的,语法不同而已
绑定从层次上分为3种: injectionbinding ,commandbinding,
mediationbing
注入绑定injectionbinding主要是用来绑定该类型对象到上下文,这样使得程序中各个地方可以通过contextview访问得到该对象。这种绑定会生成对象。这种绑定是为了生成对象并且注入到指定对象中用的
commandbinding是为了将命令绑定到方法中用的
mediationbing则是为了拦截view消息,而将view注入中介mediator中,然后在view的awake方法里面生成meidtaor对象
在绑定扩展中最接近控制反转的思想是注入
接口本身没有实现方法,只定义类中的规则
如果采用上面的写法,Spaceship类里面不用再写检测输入的功能了,只需要处理输入就可以了input只需要控制移动,不需要管是何种输入方式 是手柄键盘或是其他 只需要进行处理
也不需要武器的逻辑,仅仅是注入武器实例就可以了。但是我们需要知道武器是什么样的武器 不同的武器造成不同的掉血 所以这块的逻辑是需要处理的
在ISpaceship中的代码进行一点修改
加上Inject标签 这样就可以进行绑定了 将接口与类绑定来实现接口
[Inject]标签实现接口,而不是实例化类
在绑定多个的时候就需要利用 名称映射来进行区分
在[Inject]标签处 也需要进行添加名称
具体还有几种映射就不说了 需要的可以去看看文档
反射列表
反射所有已经通过injectionBinder映射的所有
dispatcher相当于观察者模式中的公告板,允许客户监听他,并且告知当前发生的事件。在strangeioc中,通过EventDispatcher方式实现,EventDispatcher绑定触发器来触发带参数/不带参数的方法 触发器通常是String或枚举类型(触发器可以理解为key,或者事件的名称,名称对应着触发的方法)
如果有返回值,他将存在IEvent,一个简单的值对象包含与该事件相关的任何数据,你可以写你自己的事件满足IEvent接口,strangeioc事件叫TmEvent
如果你再使用MVCSContext 有一个全局的EventDispatcher 叫contextDispatcher 会自动注入,你可以用来传递事件
有两种基本的事你可以去做EventDipatcher调度事件和监听他们
事件会处于监听状态,知道FIRE_MISSILE事件被处罚,然后执行对应的onMissileFire方法
也可以通过枚举实现
移除监听
更新监听
调用的方法可以有一个参数或者没有,这取决于你关心的事件效率
调度事件
这种形式的调度将生成一个新的TmEvent 调用任何监听对象,但是因为你没有提供数据,数据字段的TmEvent当然会是零。 你也可以调度和提供数据:
可以自己创建TmEvent调度
除了绑定事件的方法,可以将其绑定到Commands。命令是控制器结构MVC的指挥者在strangeioc的MVCSContext中 CommandBinder监听每一个dispatcher的调度(当然你可以改变这个如果你想在自己的上下文)。信号,下面描述,也可以绑定到命令。当一个事件或信号被调度,
但异步命令, 像网络请求 可以这样做
如果使用完不进行Release()可能会导致内存泄露
映射命令
您可以将多个命令绑定到单个事件如果你喜欢
解除命令绑定Unbind
一次性的指令
按顺序执行绑定 InSequence 会一直执行到最后的命令 或者其中一个命令失败。 命令可以在任何时候调用Fail() 这会打破这个序列 这可以用于建立一个链相关的事件 为构建有序的动画,或制定一个守卫,以确定是否应该执行一个命令。
信号是一个调度机制,另一种选择EventDispatcher 相比于EventDispatcher 信号有两个优点 1. 分发结果不再创建实例,因此也不需要GC回收更多的辣鸡 2. 更安全 当消息与回调不匹配时会断开执行,官网也推荐使用Singal来兼容后续版本
创建两个信号,每一个都有一个参数
消息最多可以使用四个参数
子类可以编写自己的信号
这告诉strangeioc 我们做了默认CommandBinder SignalCommandBinder取而代之。 所以是信号触发命令 而不是事件 。 strangeioc 只支持 事件或者信号中的一个映射命令,而不是两个都是。
信号绑定 依旧使用commandBinder
映射一个信号到命令 会自动创建一个injection映射 你可以通过[Inject]标签 检索
在ShipMediator,我们注入信号,然后调度
派遣一个信号通过SignalCommandBinder映射结果的实例化ShipDestroyedCommand:
如您所见,映射的方法非常类似于信号命令的方法使用事件
两个重要问题:第一,而信号支持多个相同类型的参数,注射。 因此不可能对一个信号与相同类型的两个参数映射到一个命令
映射没有命令的信号
一个信号映射到一个命令会自动创建一个映射,您可以检索通过注入到其他地方 但是如果你想注入信号没有绑定到一个命令 使用injectionBinder只需将它映射
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)来做这部分功能
1.DashboardView注入可以使 Mediator 知道具体的view
2.注入完成后OnRegister()方法会立即执行,可以用这个方法来做初始化 像上面做的那样 初始化 然后做重要的数据请求
3.contextDispatcher可以扩展任何的EventMediator
4.OnRemove()清理时使用,当一个view销毁前被调用,移除时记得删除你的监听
5.例子中的view暴露两个接口init()和updatePlayerCount(float value),但是程序在设计时 你需要更多,但是原则是相同的 限制中介除了薄任务之间的传递信息的查看和其他应用程序
绑定一个界面到Mediator
值得注意的几点
1.不是所有的MonoBehaviour被限制为一个View
2.中介者绑定是实例对实例的,也就是说一个view对应一个mediator,如果有很多view,也就会有很多的mediator
MVCSContext包含EventDispatcher(事件分发),injectionBinder(注入绑定),MediationBinder(中介绑定),CommandBinder(命令绑定)
可以重新将CommandBinder绑定到SignalCommandBinder 命令和中介依托注入,context可以为命令和中介的绑定提供关联
建立一个项目,需要重新MVCSContext或者Context,一个app也可以包换多个Contexts 这样可以使你的app更高的模块化,因此,一个app可以独立的设计为聊天模块,社交模块 最终他们会整合到一起成为一个完整的app
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 是一个Monobehaviour 用来实例你的Context(上下文) MyFirstProjectRoot 是ContextView的子类, 这里是应用程序的开始
这里要使用 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()继续
Context(上下文)是所有绑定发生的地方,如果没有绑定,Strange应用只是一堆断开连接的部分。Context是为混乱带来秩序的胶水。从我们扩展MVCSContext,我们得到了一大堆的核心绑定,MVCSContext是为了给我们所有我们需要干净的结构 一个控制反转风格的应用:一个注射(injector)、命令总线、模型和服务支持,和中介界面。
像你看到的那样,我们扩展了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)。
ContextEvent.START 被处罚,因为它被绑上了StartCommand, 一个新的StartCommand实例将被实例化出来并且执行。
StartCommand 扩展 EventCommand 意味着这是固定的命令CommandBinder可以处理, 他继承的所有东西都来自command 和 EventCommand。特别是,继承EventCommand意味着你得到一个IEvent注入,并且你可以访问dispatcher。
如果你只是扩展命令,您不会有自动访问这些对象,但是你依旧可以手动注入他们
注意所使用的两种不同类型的注入。IEventDispatcher和GameObject 都是用名字创建多个实例。这是因为我们想引用这些对象的非常具体的版本。我们不希望是任意一个GameObject。我们需要一个标记像ContextView。我们也不接受任何旧IEventDispatcher。唯一一个将在上下文间通信,他标志为ContextKeys.CONTEXT_DISPATCHER。另一方面,Ievent是一个简单的映射用于这个特殊的命令(技术上他映射到一个value),所以没有必要的名字。
依赖我们将使用在当前场景是ContextView,他们添加子视图到它。
Execute()方法通过CommandBinder自动触发。大多数情况下 , 执行的顺序是这样的
实例化Command命令绑定到Ievent.type
注入依赖关系,包括Ievent本身
调用Excute()
删除Command命令
命令不需要立即清理干净,但是我们将会得一点。如果你查看了Execute()里面的代码,你将会发现他是纯粹的Unity。创建一个GameObject,附上MonoBehaviour,然后设置它的父亲为ContextView。我们使用的是具体的MonoBehaviour(代码),然而,恰好是一个Strange IView,自从我们在context中映射这个界面。
这个界面是自动调度的,这意味着一个新的ExampleMediator刚刚创建!
如果你花费了一些时间为Unity编写代码,你创建一个界面,你需要调用Monobehavior,但是重点在于那个界面没有在屏幕上显示的东西。我不打算花时间执行ExampleView代码。你可以看下示例文件,如果怕你已经知道C#和Unity你不需要他。我只想引起两位的注意力。首先:
通过扩展View,你将会得到连接每个View到Context的代码。使用Strange 你不再需要扩展View或者重写里面的方法。但是如果你不扩展View,你依旧需要实现IView 接口。这需要确保你MonoBehaviour上下文可以操作。
第二项指出
注意 我们注入IEventDispatcher。但是跟StartCommand不是同一个调度。仔细看看代码第一个写在EventCommand(我上面显示)是这样的
通过命名注入,指定的命令使用常见的上下文调度员。这个界面不应该注入dispatcher。中介的目的是隔离应用程序的视图 反之亦然Strange允许注入View。但这功能最好的时候严格限制,注入本地调度员与中介沟通很好。所以注入配置/布局文件(这是有用的,如果你发布到多个平台)。但如果你听我的劝告,不要注入入一个模型或服务或其他池外扩展的视图以及中介。
告诉你正确的方法:对于大多数开发人员来说,最难的是掌握整个框架的概念。一个视图应该只显示和输入。当某些输入发生,视图应该通知媒体。中介Mediator(允许注入上下文调度员)抽象的观点,关注与应用程序的其余部分。这个保护应用程序的视图代码,这通常和保护你的界面是混乱的,相反的情况是如此。
注意,基本视图类使用标准MonoBehaviour处理程序
观察调度者
在最上方 我们注入了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事件。
让我们看回Context 这行有我们忽略的问题:
这里的意思是任何时候公共的总线收到这个事件,它会启动CallWebServiceCommand。但是他吸引你的注意力是因为通过不同的方法使用命令。
我们监听service,调用一个方法。我们使用事件中有效data数据来触发mediator调度者service.Request(the url)。当service结束,他派发。触发onComplate()。我们取消监听映射,设置一个值的模型,派发SCORE_CHANGE那个哪个调度者收到就做相应的处理。
但如果你一直密切关注你会记得,我之前提到过,命令后立即清理干净在Execute()完成时。所以为什么不是这个命令不会被垃圾收集。答案是最顶端调用Retain()方法保留。Retain()标志着这个命令为免除清理。这将会保持知道调用了Release()之后。显然,这意味着调用了Release()是非常重要的,否则会造成运行中的内存泄露风险。
一般来说你要遵守上下文边界。毕竟,它的边界是有原因的 :它允许应用程序的部分功能的隔离,使程序变得更加模块化。但有时有一些对象,也许是一个模型、一个服务、或者一个信号需要需要跨多个上下文访问。
添加CrossContext()信号绑定需要实例化穿过context边界。它将提供给所有孩子contexts。注意,也可以覆盖一个CrossContext绑定。如果你隐射局部的key,本地的绑定将会覆盖CrossContext的那一个。
到这里所有的文档内容已经结束了。自己着手做几个小栗子能快速的了解和适应这样的架构。架构的学习 确实可以改变人的编码习惯 。 就这几天对文档的阅读,感觉收获良多 。 希望有学习的小伙伴能够一起交流交流。
文档坐标 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 Mediatorview就是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
MVCSContex :the big picture
1.应用程序的入口是一个类成为ContextView,这是一个Monobehavior实例化MVCSContext2.用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自动触发。大多数情况下 , 执行的顺序是这样的
实例化Command命令绑定到Ievent.type
注入依赖关系,包括Ievent本身
调用Excute()
删除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的那一个。
到这里所有的文档内容已经结束了。自己着手做几个小栗子能快速的了解和适应这样的架构。架构的学习 确实可以改变人的编码习惯 。 就这几天对文档的阅读,感觉收获良多 。 希望有学习的小伙伴能够一起交流交流。
相关文章推荐
- 总结Unity IOC容器通过配置实现类型映射的几种基本使用方法
- 使用Microsoft的IoC框架:Unity来对.NET应用进行解耦
- StrangeIoC —— Unity MVC 专属框架
- .NET Unity IOC框架使用实例
- .NET Unity IOC框架使用实例详解
- StrangeIoc MVCS框架第一课:初步理解
- Unity StrangeIoc框架 (二)
- 【StrangeIOC框架】 通过一个实例来理解MVCS结构
- StrangeIOC MVCS框架介绍及进阶
- Unity StrangeIoC框架
- Unity IOC容器通过配置实现类型映射的几种基本使用方法
- Unity StrangeIoc框架 (一)
- unity(c# ioc框架) 使用总结
- StrangeIoc MVCS游戏框架
- .NET Unity IOC框架使用实例
- StrangeIoC —— Unity MVC 专属框架
- StrangeIOC MVCS框架介绍及进阶
- 常量,字段,构造方法 调试 ms 源代码 一个C#二维码图片识别的Demo 近期ASP.NET问题汇总及对应的解决办法 c# chart控件柱状图,改变柱子宽度 使用C#创建Windows服务 C#服务端判断客户端socket是否已断开的方法 线程 线程池 Task .NET 单元测试的利剑——模拟框架Moq
- 使用工厂方法和Unity实现IoC注入
- Unity StrangeIoc框架 (三)signal信号方式