Unity StrangeIoc框架 (二)

MVCSContex :the big picture 

1.应用程序的入口是一个类成为ContextView,这是一个Monobehavior实例化MVCSContextapi

2.用MVCSContext来执行各类绑定。架构

3.派发器是一个通讯总线,容许你再程序发送消息,在mvcscontext中他们发送的是TimEvents, 或者你能够按照上面的步骤重写Context 来使用Signalsmvc

4.命令类由TimeEvents或信号触发,而后他会执行一些app逻辑。app

5.模型存储状态框架

6.services与app意外的部分通讯(好比接入的faebook)ide

7.界面脚本附加到物体上 : 玩家与游戏的交互模块化

8.Mediators(中介)也是monobehavior  可是他也能够将view部分和其余部分隔离开来函数

这张图战士了这些部分是如何一块儿工做的布局

大体介绍完了  下面来如何创建工程post

一个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的那一个。

 

到这里全部的文档内容已经结束了。本身着手作几个小栗子能快速的了解和适应这样的架构。架构的学习 确实能够改变人的编码习惯 。  就这几天对文档的阅读,感受收获良多 。 但愿有学习的小伙伴可以一块儿交流交流。