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 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 {getset,}

 

    [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{ getset; }

}

 

使用另一个类来实现这个接口:

 

class Spaceship : ISpaceship

{

    public void input(float angle,float velocity)

    {

        //do stuff here

    }

    IWeapon weapon{ getset; }

}

 

 

如果采用上面的写法,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 { getset; }

 

或者:

 

[Inject("ShouQiang")]

public IWeapon weapon { getset; }

 

 

 

 

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 { getset; }

        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 { getset ;} 

        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{ getset; }

 

        [inject]

        public int basePointValue{ getset; }

 

        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 { getset; } 

        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)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值