unity StrangeIoc框架總結

概念

StrangeIoc 是依據控制反轉和解耦原理設計的,支持依賴注入。

控制反轉即Ioc(Inversion of Control) 它把傳統上由程序代碼直接操控的對象的調用權交給容器,通過容器來實現對象組件的裝配和管理。所為的“控制反轉”概念就是對組件對象控制權的轉移,從程序代碼本身轉移到了內部的容器。

依賴注入(Dependency Injection) 依賴注入的基本原則是:應用組件不應該負責查找資源或者其他依賴的寫作對象。配置對象的工作應該由Ioc容器負責。

先用一個小例子解釋一下什么是依賴注入:

如果設計一個玩家攻擊敵人的游戲,玩家可以裝備1.手槍(攻擊力10),2.步槍(攻擊力20)。最簡單的代碼實現如下:


如上代碼,假如項目中多一個武器(機槍),那么就需要重寫Player類,還要對(攻擊邏輯)fi..else進行修改,所以設計不合理。

重新設計代碼,可以用以下方式實現:


如果再增加機槍,只需要增加一個機槍類繼承IGun接口就可以在Player中:

Public IGun gun=new JiQiang();

上面這種方式簡單實現了依賴注入,使得大家都依賴接口編程,如果大家想詳細理解依賴注入的思想,可以多找些類似的資料,這篇文章偏重StrangeIoc的框架講解,這里依賴注入的內容不詳細多說!

流程

strangeioc框架圖

描述一個最基本的業務流程:

1.玩家點擊了一下UI
2.mediator得到通知(回調),因為它綁定了UI事件
3.mediator觸發了一次command執行指令,因為mediator和command進行了綁定
4.command執行自己的execute方法
5.請求Service從一個文件中讀取數據
6.Service讀取數據完成后,觸發事件通知Command執行execute函數。
7.command會把加載后的數據送給Model
8.Model檢測到自己更新數據后,通知mediator說有新數據來了
9.Mediator就會把數據送給UI,顯示到界面上。

原則:

View應該只負責顯示和輸入,當輸入事件發生時,view應當通知mediator,絕不能讓View直接通知給Model和Service,View只會分發事件,例如某個按鈕按下了,至于對這個按鈕的響應,應該交給Mediator來做,例如view告知說幫助按鈕按下了,mediator知道后,調用具體的command再去執行具體的任務。Model和Service是被Command來使用的,而不會去監聽view的事件。
下面進入strangeioc框架的詳細講解:

框架介紹

1.Bingding(綁定)

strange的核心是綁定,我們可以將一個或多個對象與另外一個或多個對象綁定(連接)在一起,將接口與類綁定來實現接口,將事件與事件接收綁定在一起。或者綁定兩個類,一個類被創建時另一個類自動創建。

strange的binding由兩個必要部分和一個可選部分組成,必要部分是a key and a valuekey觸發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 ,CommandBinder, MediationBinder

Injectionbinding:主要是用來綁定該類型對象到上下文,這樣使得程序中各個地方可以通過contextview訪問得到該對象。這種綁定會生成對象。這種綁定是為了生成對象并且注入到指定對象中用的。

commandbinding:是為了將命令綁定到方法中用的。

MediationBinder:則是為了攔截view消息,而將view注入中介mediator中,然后在view的awake方法里面生成meidtaor對象。

2.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中的代碼進行一點修改,加上Inject標簽 這樣就可以進行綁定了 將接口與類綁定來實現接口,代碼如下:

interface ISpaceship
{
    void input(float angle, float velocity);  
    [Inject]        
    IWeapon weapon{get;set;}   
}

下面[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)] 
public ISocialService socialService{get;set;}

值的映射

Configuration myConfig = loadConfiguration();
injectionBinder.Bind<IConfig>().ToValue(myConfig);

具體還有幾種映射就不多說,可以查看具體官方文檔。

3.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));
int count = injectionBinder.Reflect (list);

反射所有已經通過injectionBinder映射的所有
injectionBinder.ReflectAll();

4.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);

5.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(),如果使用完不進行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();
        }
    }
}
  • 映射命令
    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>();

6.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
        }
    }
}

如您所見,映射的方法非常類似于信號命令的方法使用事件。

兩個重要問題:第一,而信號支持多個相同類型的參數,注射。 因此不可能對一個信號與相同類型的兩個參數映射到一個命令

//正確用法
Signal<int, int> twoIntSignal = new Signal<int, int>();
twoIntSignal.AddListener(twoIntCallback);

//錯誤用法
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();

7.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

8.The context extension(上下文擴展)

MVCSContext包含EventDispatcher(事件分發),injectionBinder(注入綁定),MediationBinder(中介綁定),CommandBinder(命令綁定)

可以重新將CommandBinder綁定到SignalCommandBinder 命令和中介依托注入,context可以為命令和中介的綁定提供關聯

建立一個項目,需要重新MVCSContext或者Context,一個app也可以包換多個Contexts 這樣可以使你的app更高的模塊化,因此,一個app可以獨立的設計為聊天模塊,社交模塊 最終他們會整合到一起成為一個完整的app。

項目(demo)詳解

MVCSContex :the big picture

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開始

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

以上就是所有總結的內容!!

參考

本人才疏學淺,如有錯誤歡迎指正!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,868評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,025評論 25 708
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,937評論 6 342
  • 今天下班約了個朋友L見面,L是我的小學同學,小時候玩得很好,慢慢的,越長大聯系得就越少了,最近聯系得比較頻繁,約好...
    貳佰沒有伍_b84f閱讀 218評論 1 1
  • 我們傾向于用“光環”或“邪惡”的框架去評價我們遇見的人。這種傾向是可以理解的, 就像我老師說的,極端化是更簡單的。...
    Visianlee閱讀 519評論 3 3