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

 

暫時記錄到這里 剩下的 下一章補完


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM