StrangIOC框架一
正式之前先說說基礎的概念:
首先是MVC框架,MVC框架的思路是將業務邏輯、資料、介面顯示分離,網上很多關於MVC的資料,這裡不再過多的描述了。
下面截圖來自wikipedia的mvc框架:
然後是控制反轉(包括依賴注入和依賴查詢),控制反轉也是著名框架spring框架的核心。
對於控制反轉(Inversion of Control,縮寫IOC)的依賴注入,先用一個C#示例程式碼演示一下。比如設計一個玩家攻擊敵人的遊戲,玩家可以裝備手槍,步槍。最簡單的程式碼設計實現如下:
public class Player
{
public string gunName = "ShouQiang";
public void Attack(Enemy enemy)
{
if(gunName == "ShouQiang")
enemy.hp -= 10;
else if(gunName = ''BuQiang")
ememy.hp -= 20;
}
}
如上述程式碼,假如專案中多了一個(機槍),需要重寫player類,重寫攻擊邏輯(if..else)設計不合理。重新設計,程式碼可以用下面的方實現:
public interface IGun {
voidAttack(Enemy eneny);
}
public class ShouQiang : IGun
{
public void Attack(Enemy eneny)
{
enemy.hp -=10;
}
}
public class BuQiang : IGun
{
public void Attack(Enemy enemy)
{
enemy.hp -= 20;
}
}
public class Player
{
public IGun gun = newShouQiang();
public
{
gun. Attack(enemy);
}
}
public class Eneny
{
public int hp;
}
如果再增加機槍,直接增加一個機槍類繼承IGun介面就可以了 playe類中
public IGun gun = new ShouQiang();簡單實現了一個注入.
下面正式介紹strangeioc.
正如strangeioc描述的一樣,總體上說它是根據控制反轉和解耦和原理設計的,支援依賴注入. 繼續引入一個示例,如果寫一個單玩家遊戲,死亡後將最高分數提交到facebook ,程式碼如下:
public class MyShip: MonoBehaviour {
private FacebookService facebook;
private int myScore,
void Awake()
{
facebook = FacebookService.getlnstance(),
}
void onDie()
{
if (score > highScore)
facebook.PostScore(myScore),
}
}
這樣做有兩個問題:
1.ship作為控制角色,但是除了要通知facebook上傳分數還要處理程式碼邏輯.將自身與遊戲邏輯與 facebook邏輯關聯(髙耦合)
2.FacebookService是單鍵,如果我們改為要提交到Twitter..or Google+... 怎麼辦?或者FacebookService裡面的方法有變更怎麼辦?
如果程式碼如上,需要重構很多程式碼才能完成相應的功能,但是通過引入ioc,重構將會方便很多。
public class MyShip : MonoBehavior
{
void OnDie()
{
dispatcher.Dispatch(GameEvent.SHIP_DEAD);
}
}
檢視模組出發了死亡邏輯,它(View)需要知道的僅僅是這些就夠了,然後將死亡訊息傳送出去。
某個模組的程式碼用來接收死亡訊息,然後做出處理,如下:
public class OnShipDeathCommand: EventCommand
{
[Inject]
ISocialService socialService {get, set,}
[Inject]
IScoreModel scoreModel {get set,}
public override void Execute()
{
if (scoreModel.score > scoreModel.highScore)
socialService.PostScore(scoreModel.score);
}
}
[Inject]標籤實現介面,而不是例項化類
#if UNITY_ANDROID
injectionBinder.Bind<ISocialService>().To<GoogleService>().AsSingleton();
#else
injectionBinder.Bind<ISocialService>().To<FacebookService>().AsSingleton();
#endif
//...or...
//injectionBinder.Bind<ISocialService>().To<MultiServiceResolver>().AsSingleton();
injectionBinder.Bind<IScoreModel>() To<ScoreMcxiel>() AsSIngleton();
commandBinder.Bind(GameEvent.SHIP_DEAD , OnShipDeathCommand);
用IOC(控制反轉)我們不用關注具體邏輯,也不同自己再建立單鍵。
ship觸發Command來執行邏輯,每個模組之間都是獨立的。
1>commandBinder.Bind(GameEvent.SHIP_DEAD , OnShipDeathCommand);
將事件和對應邏輯繫結在了一起。
2>dispatcher.Dispatch(GameEvent.SHIP_DEAD)
就是發出事件,但是具體做什麼事情不需要了解。
Unity StrangeIoC(二)
strangeioc第一大部分:1.Binding(繫結)
strange的核心是繫結,我們可以將一個或者多個物件與另外一個或者多個物件繫結(連線)在一起。將介面與類繫結來實現介面,將事件與事件接受繫結在一起。或者繫結兩個類,一個類被建立的時候另一個自動建立。如果你再Unity裡面用過SendMessage方法,或者一個類引用另一個類,再或者式if...else語句,從某種形式上來說也是一種繫結。
但是直接繫結是有問題的,導致程式碼不易修改(不再是高聚合低耦合).比如一個spaceship類,裡面包含gun發射和鍵盤控制功能,當需求變更為使用滑鼠控制時,你就需要重新修改spaceship方法,但是僅僅是控制功能的需求改變了,為什麼要重寫spaceship方法呢?
如果你自定義一個滑鼠控制類(mousecontrol)。然後spaceship裡面呼叫滑鼠控制類,你還是採用的直接繫結的方法。當需求變為鍵盤控制的時候,你又要去重新修改spaceship去呼叫鍵盤控制的class。
strange可以實現間接繫結從而使你的程式碼減輕對其他模組的依賴(低耦合)。這是面向物件程式設計(Object-Oriented Programming)的根基和宗旨。通過strangeioc繫結你的程式碼會變得更加靈活。
The structure of a binding
strange的binding由兩個必要部分和一個可選部分組成,必要部分是a key and a value, key觸發value,因此一個事件可以觸發回撥,一個類的例項化可以觸發另一個類的例項化。可選部分是name,它可以區分使用相同key的兩個binding。下面的三種繫結方法其實都是一樣的,語法不同而已:
1.Bind<IDrive>().To<WarpDrive>();
2.Bind(typeof(IDrive)).To(typeof(WarpDrive));
3.IBinding binding = Bind<IDrive>();
bingding.To<WarpDrive>();
bind是key,to是value。
當使用非必要部分Name時,如下:
Bind<IComputer>().To<SuperComputer>().ToName("DeepThought");
MVCSContext式推薦的版本,它包含下面介紹的所有拓展,是使用strange最簡單的途徑
strangeioc第二大部分:2.Extensions
看strange的介紹,它是一個依賴注入框架,雖然它具有依賴注入的功能,但是核心思想還是繫結。
接下來講介個關於binder(繫結)的擴充套件。
The injection extension(注入)
在binder擴充套件中最接近Inversion-of-Control(IoC)控制反轉思想的是注入。
大家應該很熟悉介面的使用方法,介面本身沒有是實現方法,只是定義類中的規則:
interface ISpaceship
{
void input(float angle,float velocity);
IWeapon weapon{ get; set; }
}
使用另一個類來實現這個介面:
class Spaceship : ISpaceship
{
public void input(float angle,float velocity)
{
//do stuff here
}
IWeapon weapon{ get; set; }
}
如果採用上面的寫法,Spaceship類裡面不用再寫檢測輸入(input)的功能了,只需要處理輸入就可以了,不再需要武器邏輯,僅僅式注入武器例項就可以了。到現在已經邁出一大步了。但是出現了一個問題,誰告訴spaceship,我們使用何種武器?我們可以採用工廠模式給weapon賦值具體的武器例項,簡單的工廠模式如下:
public interface IWeapon
{
void Attack();
}
public class BuQiang : IWeapon
{
public void Attack(){//敵人掉血邏輯
}
}
public class ShouQiang : IWeapon
{
public void Attack() {//敵人掉血邏輯
}
}
上面建立好了武器的約定介面和兩種武器,下面建立武器工廠:
public class MeaponFactory
{
public IWeapon GetWeapon(string name)
{
if(name == "BuQiang")
return new BuQiang();
else if (name == "ShouQiang")
return new ShouQiang();
return null;
}
}
取武器直接使用
IWeapon weapon = new WeaponFactory().GetWeapon("BuQiang");
(ps. 也有人說,取武器直接IWeapon weapon = new BuQiang();使用工廠反而麻煩,但是如果我們可以從美國軍械庫去到步槍,手槍。中國軍械庫也可以去到步槍,手槍。使用工廠模式就方便了許多:
IWeapon weapon = new AmericaWeaponFactory().GetWeapon("BuQiang");
IWeapon weapon = new ChinaWeaponFactory().GetWeapon("BuQiang");
但是這樣做並沒有解決根本問題,factorymanager需要包括/返回各種武器的例項。現在我們引入一個全新的模式:Dependency Injection(DI)依賴注入:
DI模式中一個類對另一個類的引用,通過發射的方式實現(比如我們的ISpaceship中的weapon不再直接複製類的例項/或者通過工廠賦值類的例項(這樣它們之間仍舊存在引用關係))而是通過反射(Reflection)的方式實現。
首先我將IWeapon和具體的手槍例項繫結:
injectionBinder.Bind<IWeapon>().To<ShouQiang>();
然後ISpaceship中weapon賦值如下:
[Inject]
public IWeapon weapon { get; set; }
當然如果有多個實現方式可以使用如下方式:
injectionBinder.Bind<IWeapon>().To<ShouQiang>().ToName("ShouQiang");
injectionBinder.Bind<IWeapon>().To<BuQiang>().ToName("BuQiang");
然後ISpaceship中weapon賦值如下:
[Inject("BuQiang")]
public IWeapon weapon { get; set; }
或者:
[Inject("ShouQiang")]
public IWeapon weapon { get; set; }
Unity StrangeIoC(三)
第二個擴充套件部分:
The reflector extension(反射)
List<Type> list = new List<Type>();
list.Add(tpyeof(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.ReflectAll();
下面接著說擴充套件部分中的第三個擴充套件(排程器)
The dispatcher extension
從原理上來說dispatcher相當於觀察者模式中的公告板,允許客戶監聽他,並且告知當前發生的事件。但在strangeioc中,通過EventDispatcher方式實現,EventDispatcher繫結觸發器來觸發帶引數/不帶引數的方法。
觸發器通常是string或者是列舉型別(觸發器你可以理解為key,或者事件的名稱,名稱對應著觸發的方法).
如果事件有返回值,他將存在IEvent,編輯邏輯實現這個介面就能得到返回值,標註的strangeioc事件叫做TmEvent.如果你在用MVCSContext,有個全域性的EventDispatcher叫:contextDispatcher會自動注入,你可以用它來傳遞事件。
There's also acrossContextDispatcher for communicating between Contexts。
同樣有一個accossContextDispatcher可以在contexts傳遞事件。
使用EventDispatcher需要做兩個事情就可以,排程/設定事件和監聽事件。
dispatcher.AddListener("FIRE_MISSILE",onMissleFire);
然後事件就會處於監聽狀態,直到FIRE_MISSLE事件被觸發,然後執行相應的onMissleFire方法。
當然Key也可以不適用字串而是列舉型別。
相關的幾個Api:
dispatcher.AddListener("FIRE_MISSILE",onMissleFire);
dispatcher.RemoveListener("FIRE_MISSILE",onMissleFire);
dispatcher.UpdateListener("FIRE_MISSILE",onMissleFire);
派發訊息:
dispatcher.Dispatch("FIRE_MISSLE")。
Unity StrangeIoC(四)
繼續擴充套件部分的第四個擴充套件(命令)
The command extension
除了將事件繫結到方法以外,還可以將他們與命令繫結。命令式mvc框架的指揮者。
在strangeioc的MVCSContext中,CommandBinder監聽所有來自dispatcher分發的事件.
Signals(上文提到的訊息(signal)與事件(event))也可以繫結到command.
using strange.extensions.command.impl;
using com.example.spacebattle.utils;
namespace com.example.spacebattle.controller
{
class StartGameCommand : EventCommand
{
[Inject]
public ITimer ganeTimer { get; set; }
overridepublic void Execute()
{
gameTimer.start();
dispatcher.dispatch(GameEvent.STARTED);
}
}
}
如果是有網路請求,有返回,可以採用這樣的方法實現:
using strange.extensions.command.impl;
using com.example.spacebattle.service;
namespace com.example.spacebattle.controller
{
class PostScoreCommand : EventCommand
{
[Inject]
IServer gameServe { get; set ;}
override public void Execute()
{
Retain();
int score = (int)evt.data;
gameServer.dispatcher.AddListener(
ServerEvent.SUCCESS, onSuccess);
gameServer.dispatcher.AddListener(
ServerEvent.FAILURE, onFailure);
gaweServer.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 sowething to report failure...
Release();
}
}
}
將命令和該類繫結起來
commandBinder.Bind(ServerEvent.POST_SCORE).To<PostScoreCommand>();
命令繫結多個事件:
commandBinder.Bind(GameEvent.HIT).To<DestroyEnemyCommand>()
.To<UpdateScoreCommand>();
解除繫結:
commandBinder.Unbind(GameEvent.POST_SCORE);
按順序觸發命令繫結事件:
commandBinder.Bind(GameEvent.HIT).InSequence()
.To<CheckLevelClearedCommand>()
.To<EndLevelCommand>()
.To<GameOverCommand>();
Unity StrangeIoC(五)
擴充套件部分的第五個擴充套件
The signal extension(訊息)
訊息是一種代替EventDispatcher的分發機制,與EventDispatcher相比,訊息1.分發結果不再建立例項,因此也不需要回收更多的垃圾。2.更安全,當訊息與回撥不匹配時,就會斷開執行。官網也推薦使用signal來相容後續版本。
Signal<int> signalDispatchesInt = new Signal<int>();
Signal<string> signalDispatchesString = new Signal<string>();
//Add a callback with an int parameter
signalDispatchesInt AddListener(callbacklnt);
//Add a callback with a string parameter
signalDispatchesString AddListener(callbackString);
signalDispatchesInt.Oispatch(42); //dispatch an int
signalDispatchesString.Oispatch("Ender Wiggin"); //dispatch a string
void callbacklnt(int value)
{
//Do something with this int
}
void callbackString(string value)
{
//Do something with this string
}
訊息最多可以使用四個引數:
//works
Signal signal0 = new Signal();
//works
Signal<SomeValueObject> signal1 = new Signal<SomeValueObject>();
//works
Signal<int,string> signal2 = new Signal<int,string>();
//works
Signal<int,int> signal3 = new Signal<int,int>();
//works
Signal<SomeValueObject,int,string,MonoBehavior> signal4 = new
Signal<SomeValueObject,int,string,MonoBehavior>();
//FAILS!!!! Too many params.
Signal<int,string,Vector2,Rect> signal5 = new
Signal<int,string,Vector2,Rect>();
訊息繫結:
commandBinder.Bind<SomeSignal>().To<SomeCommand>();
看下一個示例:
commandBinder.Bind<ShipDestroyedSignal>().To<ShipDestroyedCommand>();
然後:
[Inject]
public ShipDestroyedSignal shipDestroyedSignal{ get; set; }
// imagining that the Mediator holds avalue for this ship
private int basePointValue;
//Something happened that resulted in destruction
private void OnShipDestroyed()
{
shipDestroyedSignal.Dispatch(view,basePointValue);
}
再然後:
using System;
using strange.extensions.command.impl;
using UnityEngine;
namespace mynamespace
{
//Note how we extend Command,not EventCommand
public class ShipDestroyedCommand: Command
{
[inject]
public MonoBehavior view{ get; set; }
[inject]
public int basePointValue{ get; set; }
public override void Execute()
{
//Do unspeakable things to the destroyed ship
}
}
}
再給出一個strangeios的demo裡面的程式碼:
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;
using strange.extensions.command.api;
using strange.extensions.command.impl;
namespace strange.examples.signals
{
public class SignalsContext : MVCSContext
{
public SignalsContext (MonoBehaviour view) : base(view)
{
}
public SignalsContext (MonoBehaviour view,
ContextStartupFlags flags)
: base(view, flags)
{
}
// Unbind the default EventCommandBinder and rebind the SignalCommandBinder
protected override void addCoreComponents()
{
base.addCoreComponents();
injectionBinder.Unbind<ICommandBinder>();
injectionBinder.Bind<ICommandBinder>()
.To<SignalCommandBinder>().ToSingleton();
}
// Override Start so that we can fire the StartSignal
override public IContext Start()
{
base.Start();
StartSignal startSignal= (StartSignal)injectionBinder
.GetInstance<StartSignal>()
startSignal.Dispatch();
return this;
}
protected override void mapBindings()
{
injectionBinder.Bind<IExampleModel>()
.To<ExampleModel>().ToSingleton();
injectionBinder.Bind<IExampleService>()
.To<ExampleService>().ToSingleton();
mediationBinder.Bind<ExampleView>().To<ExampleMediator>();
commandBinder.Bind<CallWebServiceSignal>()
.To<CallWebServiceCommand>();
//StartSignal is now fired instead of the START event.
//Note how we've bound it "Once". This means that the mapping goes away as soon as the command fires.
commandBinder.Bind<StartSignal>().To<StartCommand>().Once();
//In MyFirstProject, there's are SCORE_CHANGE and FULFILL_SERVICE_REQUEST Events.
//Here we change that to a Signal. The Signal isn't bound to any Command,
//so we map it as an injection so a Command can fire it, and a Mediator can receive it
injectionBinder.Bind<ScoreChangedSignal>().ToSingleton();
injectionBinder.Bind<FulfillWebServiceRequestSignal>()
.ToSingleton();
}
}
}
signal 既可以通過command新增繫結監聽也可以通過AddLister
Unity StrangeIoC(六)
繼續擴充套件部分的下一個擴充套件
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)來做這部分的功能。
mediator示例:
using strange.extensions.mediation.ipml;
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);
dispatchers 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.updatePlayeCount(playerlist. Length);
}
}
}
示例說明:
1.當完成注入之後,DashboardView變為當前的場景所掛載的view.
2.當完成注入之後(可以理解為結構體的方式) OnRegister()方法會立即執行。可以用這個方法來做初始化。
3.contextDispatcher可以擴充套件任何的EventMediator。
4.OnRemove()清理使用,當一個view在被銷燬前呼叫,並且要移除你所新增的所有監聽器。
5.示例中的view暴力ultimate兩個介面,但是在程式設計的時候你可能會更多,但是有一條理念:中介者不處理GameObject相關屬性更新。
可以這樣來繫結:
mediationBinder.Binder<DashboardView>().To<DashboardMediator>();
注意:
1.並不是所有MonoBehavior都需要限定成view.
2.中介者繫結是例項對例項的,也就是說一個view一定是對應一個mediator,如果有多個view那麼一定有多個mediator。
繼續另外一個擴充套件:
The context extension
MVCSContext包括
EventDispatcher(事件分發),
InjectionBinder(注入繫結),
MediationBinder(中介繫結),
CommandBinder(命令繫結)。
可以重新將CommandBinder 繫結到SinalCommandBinder。 命令和中介依託注入,context以為命令和中介的繫結提供關聯。
建立一個專案需要從重新定義MVCSContext或者Context,一個app也可以包含多個Contexts,這樣可以使你的app更高的模組化,因此,一個app可以獨立的設計為聊天模組,社交模組,最終他們會整合到一起成為一個完整的app。
Unity StrangeIoC(七)
strangeioc第三大部分:3.MVCSContext
這一部分講的是如何使用MVCSContext建立一個app.
MVCSContext式通過使用strangeioc的方式,將所有的小的模組/架構,整合到一個易用的包中。它是以mvcs(s meaning service)的架構來設計的。
1.app的入口是一個monobehavior來擴充套件的mvcscontext。
2.用mvcs的子類來執行各種繫結。
3.dispatcher(分發器)可以讓你在整個app中傳送時間,在mvcscontext中它們傳送的是TmEvents,你可以重寫context來使用signal(訊息)的方式傳送。
4.commands(命令)會被IEvents或者Signal觸發,觸發之後會執行一段邏輯。
5.model儲存狀態。
6.services與app之外的部分通訊(比如接入facebook)。