• 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中,然后在viewawake方法里面生成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

    暂时记录到这里 剩下的 下一章补完

  • 相关阅读:
    养成好习惯:在控制面板里停止服务
    很吊炸天的Xcode插件,你想要的这都有
    Cscope how to support java and c++
    java_lambda表达式
    【C语言】14-返回指针的函数与指向函数的指针
    李洪强
    【C语言】13-指针和字符串
    李洪强-C语言7-C语言运算符
    【C语言】12-指向一维数组元素的指针
    李洪强-C语言6-控制结构
  • 原文地址:https://www.cnblogs.com/OrangeZhang/p/5082386.html
Copyright © 2020-2023  润新知