Unity StrangeIoc框架 (一)

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

文档坐标   http://strangeioc.github.io/strangeioc/TheBigStrangeHowTo.htmlgit

StrangeIoc 是依据控制反转和解耦原理设计的,支持依赖注入。github

控制反转即Ioc(Inversion of Control) 它把传统上由程序代码直接操控的对象的调用权交给容器,经过容器来实现对象组件的装配和管理。所为的“控制反转”概念就是对组件对象控制权的转移,从程序代码自己转移到了内部的容器。api

依赖注入(Dependency Injection)    依赖注入的基本原则是:应用组件不该该负责查找资源或者其余依赖的写做对象。配置对象的工做应该由Ioc容器负责,安全

在使用时网络

Bingding(绑定)

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对象

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