正式之前先说说基础的概念:
首先是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 void Attack(Enemy enemy)
{
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
#else
injectionBinder.Bind
#endif
//...or...
//injectionBinder.Bind
injectionBinder.Bind
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
2.Bind(typeof(IDrive)).To(typeof(WarpDrive));
3.IBinding binding = Bind
bingding.To
bind是key,to是value。
当使用非必要部分Name时,如下:
Bind
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
然后ISpaceship中weapon赋值如下:
[Inject]
public IWeapon weapon { get; set; }
当然如果有多个实现方式可以使用如下方式:
injectionBinder.Bind
injectionBinder.Bind
然后ISpaceship中weapon赋值如下:
[Inject("BuQiang")]
public IWeapon weapon { get; set; }
或者:
[Inject("ShouQiang")]
public IWeapon weapon { get; set; }
Unity StrangeIoC(三)
第二个扩展部分:
The reflector extension(反射)
List
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
命令绑定多个事件:
commandBinder.Bind(GameEvent.HIT).To
.To
解除绑定:
commandBinder.Unbind(GameEvent.POST_SCORE);
按顺序触发命令绑定事件:
commandBinder.Bind(GameEvent.HIT).InSequence()
.To
.To
.To
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
//works
Signal<int,string> signal2 = new Signal<int,string>();
//works
Signal<int,int> signal3 = new Signal<int,int>();
//works
Signal
Signal
//FAILS!!!! Too many params.
Signal<int,string,Vector2,Rect> signal5 = new
Signal<int,string,Vector2,Rect>();
消息绑定:
commandBinder.Bind
看下一个示例:
commandBinder.Bind
然后:
[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
injectionBinder.Bind
.To
}
// Override Start so that we can fire the StartSignal
override public IContext Start()
{
base.Start();
StartSignal startSignal= (StartSignal)injectionBinder
.GetInstance
startSignal.Dispatch();
return this;
}
protected override void mapBindings()
{
injectionBinder.Bind
.To
injectionBinder.Bind
.To
mediationBinder.Bind
commandBinder.Bind
.To
//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
//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
injectionBinder.Bind
.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
注意:
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)。