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

 

posted on 2017-05-04 15:00  Sun‘刺眼的博客  阅读(1767)  评论(0编辑  收藏  举报

导航