介绍Strange框架的基本方法使用与部署项目
- 应用程序的入口点是一个叫ContextView类,这是一个简单的MonoBehaviour ,用来对MVCSContext实例化。
- MVCSContext(实际上是MVCSContext的子类)是你设置绑定信息的位置
- 调度程序是一个通信总线,允许您在您的应用程序中发送消息。在MVCSContext派发的对象是TmEvents,或者,您可以按照上面列出的步骤重新对上下文使用Signals进行重写
- Commands是通过IEvents或者Signals来出发的。当命令执行时,它执行部分应用程序逻辑
- Models 存储状态
- Services和应用以外的部分沟通
- Views 是MonoBehaviours附加在GameObjects:玩家实际看到和操作的地方
- Mediators也是MonoBehaviours,但是功能非常明确,用来解耦View和应用程序的其他部分。
Binding
单一绑定。
- 一个Strange的绑定由两个必须部分和一个可选部分组成。必须的部分是一个key和一个value。Key触发value,就像一个事件可以触发一个回调。或一个类的实例化可以导致另一个类的实例化。可选部分是一个名字。在一些情况下,我们会使用相同的键来限定不同两个的绑定。在这种情况下,这个名字作为一个鉴别器。
Bind<Spaceship>().To<Liberator>();
Bind(“MeaningOfLife”).To(42);
Bind<Spaceship>().To(“Enterprise”);
Bind<IComputer>().To<SuperComputer>().ToName(“DeepThought”);
- 请注意以下的写法都是一样的:
Bind<IDrive>().To<WarpDrive>();
Bind(typeof(IDrive)).To(typeof(WarpDrive));
IBinding binding = Bind<IDrive>();
binding.To<WarpDrive>();
Extensions
框架的核心是绑定。
injection(注入)
和控制反转(IOC)最密切相关的。
- 一个类永远不需要显式地完成另一个类的依赖关系。这种模式叫做依赖注入(DI)。在依赖注入模式(DI)下,一个类请求它所需要的(理想的是一个接口的形式)和一个被称为一个注入器的类提供了它的需要。通常,这是通过一种称为反射的机制来完成的。尽量少的使用反射,速度不是很快。
- 反映出一个类时,我们可以检查它的方法和属性。我们可以看到函数签名是什么样子,他们需要什么参数。通过检索这些参数,我们可以推断出类的依赖关系是什么样子,然后返回给它。
设置一个依赖项的代码如下:
[Inject] //属性标签
public IInterface myInstance {get;set;}
如果你不使用DI,属性标签完全是无害的。所以你可以把它添加到你的类,如果有一天你发现DI不适合你的项目你可以直接将属性的[inject]标签去掉,属性还是普通的get/set
Instantiating injectable instances (实例注入)
你需要做两件事:
- 绑定类在上下文(Context)中
- 从InjectionBinder实例化实例
IClass myInstance = injectionBinder.GetInstance<IClass>() as IClass;
Types of injection mapping(类型注入映射)
- 比较常用的单例的绑定实现
injectionBinder.Bind<ISocialService>().To<TwitterService>().ToSingleton();
因为Strange的依赖仅仅是一个映射。一旦你使用了DI,你就不在需要使用单例,你只需要使用单例映射。
- 第二种类型的映射:名称注入
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 (ServiceTypes.TERTIARY)] //We mapped TwitterService to TERTIARY
public ISocialService socialService{get;set;}
注入的名称可以是任何类型,但实际运用中枚举是一个不错的选择。建议谨慎使用此功能。
- 值映射
有时你想要确切地知道你的注入。也许你在正在不同的程序域中加载配置文件。
Configuration myConfig = loadConfiguration();
injectionBinder.Bind<IConfig>().ToValue(myConfig);
myConfig将加载一些配置文件的结果。在你需要的地方使用IConfig,您将收到myConfig值。再一次,请注意,客户端类不知道是否这是一个单例,一个值,等等。它的工作是使用IConfig,而不知道它从哪里来。
- 无法控制一个类。也许它来自一个你下载的程序库,并且已经是一个单例模式的存在。可以使用ToValue映射来完成单例绑定。Get()取得实例(也许在上下文)然后映射结果。
TouchCommander instance = TouchCommander.Get();
injectionBinder.Bind<TouchCommander>().ToValue(instance);
注入类的同时你可以做一些事
- 设置注入
//使用属性注入,使用[Inject] 属性标签。
[Inject]
public ICompensator compensator{get;set;}
//名称注入
[Inject(CompensatorTypes.HEISENBERG)]
public ICompensator compensator{get;set;}
//标记成一个类的类型
[Inject(typeof(HeisenbergMarker))]
public ICompensator compensator{get;set;}
- [Construct]
如果你的类有多个构造函数你可以用[Construct]来标记,让StrangeIoc执行的构造函数,如果你没有加[Construct]标签的话,StrangeIoc默认执行构造函数的参数列表参数最少的函数,如果你只有一个构造函数那么相应不需要加[Construct]标签 - [PostConstruct]
任何方法被[PostConstruct]标记,在setter(属性注入)注入完成之后调用,它允许你在注入工作完成之后调用,他是一个安全类型不会返回空指针。
如果你有多个[PostConstruct]标签,你可以在参数列表内指定执行顺序
[PostConstruct(1)]
…
[PostConstruct(2)]
…
The reflector extension(反射扩展)
反射是在运行时分析类的过程。StrangeIoC通过这个过程来确定注入内容。
//反射类型列表
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);
The
//一切都被映射到了InjectionBinder
injectionBinder.ReflectAll();
The dispatcher extension(调度器的扩展)
StrangeIoc规范的事件叫做TmEvent
EventDipatcher里你要做两个最起初的事情,分派事件与监听
- 监听
dispatcher.AddListener("FIRE_MISSILE", onMissileFire);
//直到FIRE_MISSILE被调度器调用,OnMissileFire方法才被调用
这样不好,使用字符串作为Key会使代码变得很脆弱,换句话说就是,他们让代码很容易出错。在一个地方一个字符串可以改变代码的其余部分不知晓,这一定是一个灾难。用常量或者枚举会更好:
dispatcher.AddListener(AttackEvent.FIRE_MISSILE, onMissileFire);
- 移除监听
dispatcher.RemoveListener(AttackEvent.FIRE_MISSILE, onMissileFire);
- 基于bool变量更新Listener的方法
dispatcher.UpdateListener(true, AttackEvent.FIRE_MISSILE, onMissileFire);
The command extension
该命令是先注入,然后执行,最后释放。
CommandBinder 监听着每个调度,Signals也可以绑定到命令。
- 一个简单的命令
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);
}
}
}
- 异步调用命令如同调用一个WebService,我们有两个很简单的方法 Retain() 与 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();
}
}
}
你调用了Retain该命名保持在内存中,如果你没有调用release可能会造成内存泄漏。
Mapping commands
commandBinder.Bind(ServerEvent.POST_SCORE).To<PostScoreCommand>();
- 将多个命令绑定到一个事件上
commandBinder.Bind(GameEvent.HIT).To<DestroyEnemyCommand>().To<UpdateScoreCommand>();
- 取消绑定
commandBinder.Unbind(ServerEvent.POST_SCORE);
- 仅执行一次的命令
commandBinder.Bind(GameEvent.HIT).To<DestroyEnemyCommand>().Once();
使用Once()声明可以保证在下次调用之前销毁这个命令
- 命令组是一连串连贯的命令,命令组中命令一个接一个执行如果其中一个失败便终止立刻调用Fail()函数 ,命令组只需要调用InSequence()函数便可以使用
commandBinder.Bind(GameEvent.HIT).InSequence()
.To<CheckLevelClearedCommand>()
.To<EndLevelCommand>()
.To<GameOverCommand>();
The signal extension
请注意Strange目前支持事件或信号映射到命令,但不是两个同时一起映射。
Signal一种调度机制- EventDispatcher 的替代品。
- Signal比起EventDispatcher有有两个主要优点。
- Signal调度结果中没有创建新的对象,因此GC没有必要创建很多实例。
- 更重要的是,Signal调度是类型安全的而且Signal和其映射的回调不匹配在编译时就会报错(编译器强类型检查)
- 创建回调函数
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
signalDispatchesString.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是类型安全的,而且是向下转型,它意味着每次赋值都是一次映射
//You can do this...
Signal<SuperClass> signal = new Signal<SuperClass>();
signal.Dispatch(instanceOfASubclass);
//...but never this
Signal<SubClass> signal = new Signal<SubClass>();
signal.Dispatch(instanceOfASuperclass);
- Signal实现了最多4个参数的重载 如果有更多的参数你可以考虑包装成对象发送
//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, int> signal3 = new Signal<int, int, int>();
//works
Signal<SomeValueObject, int, string, MonoBehaviour> signal4 = new Signal<SomeValueObject, int, string, MonoBehaviour>();
//FAILS!!!! Too many params.
Signal<int, string, float, Vector2, Rect> signal5 = new Signal<int, string, float, Vector2, Rect>();
- 写Signal子类
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>
{
}
}
- 将Signal绑定到上下文(可以直接让你的Context继承SignalContext)
protected override void addCoreComponents()
{
base.addCoreComponents();
injectionBinder.Unbind<ICommandBinder>();
injectionBinder.Bind<ICommandBinder>().To<SignalCommandBinder>().ToSingleton();
}
- 绑定Signals来处理Commands
commandBinder.Bind<SomeSignal>().To<SomeCommand>();
- 创建注入映射它自动映射一个Signal到一个命令
[Inject]
public ShipDestroyedSignal shipDestroyedSignal{get; set;}
- 演示Signal/ Command 映射是如何进行
//绑定Signal来启动上下文
commandBinder.Bind<ShipDestroyedSignal>().To<ShipDestroyedCommand>();
//ShipMediator中我们注入Signal然后调用它
[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);
}
//通过Signal的Dispatching来调用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
}
}
}
- 虽然信号支持相同类型的多个参数,注入不能这样做。因此不可能为具有相同类型的两个参数的信号映射到一个命令。
- 建议使用自定义startsignal重写你的上下文启动方式:
override public void Launch()
{
base.Launch();
//Make sure you've mapped this to a StartCommand!
StartSignal startSignal= (StartSignal)injectionBinder.GetInstance<StartSignal>();
startSignal.Dispatch();
}
- 不使用命令映射Signal,注入Signal并不去绑定到命令上
injectionBinder.Bind<ShipDestroyedSignal>().ToSingleton();
The mediation(中介) extension
建议您的视图中包含至少两个不同的组件对象:View and Mediator.
- 一个Mediator的例子
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);
}
}
}
- OnRegister() 是注入后立即触发的方法。它就像一个构造函数,你可以使用它来初始化视图,并执行其他初始化过程,包括 — 正如我们正在做的 — 请求重要数据
- OnRemove() 是为清理添加过的监听 ;刚好在一个视图销毁前。请记住删除所有已经添加的侦听器。
- 绑定一个中介到视图上
mediationBinder.Bind<DashboardView>().To<DashboardMediator>();