和Keyle一起学StrangeIoc – Extensions

logo
 
Strange: the IoC framework for Unity

Extensions

You may have heard that Strange is a Dependency Injection framework. I'm a little uncomfortable with that description. Sure, Strange offers DI and it's a great use, but the core of the framework — as I've said — is binding. The installation comes with several useful extensions of the core Binder, which I'm going to detail in this section. Remember, though, that nothing stops you from extending the Binder to create your own custom implementations.

本文例举大部分实例代码都在scripts/extensions/context下可以找到,可以参见我在Overview章末给出Strangeioc源码地址以及在 practice 分支中整理出来的代码。原解决方案的格式是 mono solution,我习惯用VS就顺手整出来一份VS能直接打开的。最后两个章节都是比较核心的章节当然我也不会只翻译结论而是通篇翻译,如果有问题的地方欢迎指出。最近家里有点事所以本屌拖得比较久才写完,各位看官请见谅。

Note: in the sections that follow, I regularly refer to the MVCSContext version of Strange.MVCSContext is the recommended version, which includes all the extensions mentioned below. It’s the easiest way to get started with Strange.

The injection extension

针对注入的延伸,在Inject包内绑定延伸与控制反息息相关,我们在注入介绍中暗示过这一点,现在我们深入细节看看

The Binder extension most closely related to Inversion-of-Control (IoC) is the injector package. We hinted at injection a bit in the prior section, now let's get into the particulars.

这是一个C#接口的基本实现以及一个简单的接口实现

You may be familiar with the idea of writing Interfaces. An Interface contains no implementation itself, it just defines what a class’s inputs and outputs look like. In C# this looks like:

interface ISpaceship
{
    void input(float angle, float velocity);
    IWeapon weapon{get;set;}
}

And the class that implements the interface looks like:

class Spaceship : ISpaceship
{
    public void input(float angle, float velocity)
    {
            //do stuff here
    }
    
    public IWeapon weapon{get;set;}
}

通过实现接口我们隔离了飞船与其他具体组件的联系,面向接口编程让我们在编码过程中前进了一大步

By programming to interfaces, we relieve some of the Thing-Contains-SubThings problem. Our Spaceship no longer needs to contain a keyboard listener, it simply needs a method to react to input. It no longer needs a Gun, just something (what we call a 'concrete' class) that satisfies the IWeapon interface. That's a big step forward.

但是我有个问题想问你,你怎么知道告诉飞船类具体的IWeapon属性是被哪个类实现的,当然你可以使用GameField去获取具体的类型,但是这样又变成对特定类型实现的依赖,这种实现不怎么好。

But here's a question for you: who tells the Spaceship what type of IWeapon to use? Well, let's say the Spaceship will be in a GameField, so maybe the GameField could tell the Spaceship what weapon it would use? But that would mean that the GameField would need to know about the concrete class. All that does is shift the location of the dependency, so that's no good.

这个 GameField  可能需要一个接口将所有的依赖项转移到程序的最高层

The GameField could have an interface that pushed all of its dependencies (including everything the Spaceship needs), and so on, right up to the top of the application.

TopOfApp > GameModule > GameField > Spaceship

Phaser --------------------------------------------------->

这样做将删除具体的类,但这也意味着一个长链的依赖将贯穿整个类层次结构。首先这脆弱的,这意味着任何部分的修改可能打破这个链。它也意味着GameField(和任何其他类链中)需要知道IWeapon这个接口。但GameField并不关心IWeapon的具体实现,为什么要创建一个没必要的依赖项呢。

That would remove the concrete classes, but it would also mean a long chain of dependency pushes through the entire class hierarchy. That's brittle, meaning that a change anywhere could break lots of things and be very hard to locate. It also means that the GameField (and any other classes in the chain) needs to know about IWeapon. But GameField probably doesn't care about IWeapon, so why create a dependency where none is needed?

用工厂模式实现怎么样?如果我创建一个SpaceshipFactory,用这个类创造宇宙飞船和遵循IFactory接口,然后GameField此时就只需要一个依赖项。现在我们已经取得了一些进展。

How about a Factory pattern? If I create a SpaceshipFactory, a class that creates Spaceships and simply follows the IFactory interface, then the GameField needs only that one dependency. Now we're getting somewhere.

GameField ---------> SpaceshipFactory : IFactory

ISpaceship <---------      (creates concrete Spaceship)

我不需要知道IWeapon,虽然我需要知道ISpaceship,也需要知道IFactory。也可能需要一个IEnemy接口,再考虑下吧。的确,我需要连接所有的工厂以及搞清楚他们是如何装配实例的,所以不是很坏的实现(这是许多程序员就这样做的)。你也看到了,即使是这个成熟的设计模式有很大的弱点。

No need to know about IWeapon, though I need to know about ISpaceship and now I need IFactory too. Hmmm, and probably IEnemy, come to think of it. And, yeah, I need to wire up all those factories and figure out how they’re being provided. So not bad (and this is as far as many programmers get). But you can see that even this well-regarded pattern has significant weaknesses.

现在我们思考一个完全不同的模型吧,一个类从来没有类显式地满足另一个类的依赖性。这个模型被称为依赖注入(DI)。一个类请求它所需要的(理想情况下以一个接口的形式)和一个称为注入器的类提供。这是一种比较传统的做法,通过一种称为反射的机制来实现的。

So consider a completely different model, one where no class ever has to fulfill another class’s dependencies explicitly. This model is called Dependency Injection (DI). In DI, a class requests what it needs (ideally in the form of an Interface) and a class called an Injector provides that need. Most traditionally, this is accomplished by means of a mechanism called Reflection.

使用依赖注入的实现方式是这样的(动态注入)

With DI, if GameField needs an ISpaceship, it sets up a dependency that looks like this:

ISpaceship <---------      (as if by magic)

没有依赖链也没有依赖的工厂,你也不需要依赖具体的类(当然你也可以选择依赖具体的类)

There’s no reliance on dependency chains or factories. There are no dependencies except the ones your class actually needs. And you never need to make the dependency explicit (though of course you can choose to do so).

So how’s the “magic” work?

c# System.Reflection类库下允许一个类在运行时被拆解(反射)和分析。值得注意的是,这个过程不是很快,所以我们在StrangeIOC中要谨慎使用,即使你不是用strangeioc框架你也应该尽量少的使用反射。反映出一个类时,我们可以检查它的方法和属性。我们可以看到函数签名是什么样子,他们需要什么参数。通过检索这些参数,我们可以推断出类的依赖关系是什么样子,然后给返回给它。

C#’s System.Reflection package allows a class to be deconstructed at runtime and analyzed. It’s worth noting that this isn’t the fastest process, so we use it sparingly in Strange, and so should you. When reflecting a class, we can examine its methods and properties. We can see what its construction methods look like and what parameters they require. By examining all these clues we can deduce what a class’s dependencies look like, then provide them.

The code for setting up a dependency in Strange usually looks like this:

设置一个依赖项的代码是酱的

[Inject]
public IInterface myInstance {get;set;}

为了Strange知道IInterface的具体实现呢?因为你已经在上下文的记录中告诉它绑定的依赖,上下文指的就是MVCSContext

And how does Strange know what concrete class to provide for IInterface? You tell it by binding  dependencies in a central file called the Context. As I’ve mentioned, the “standard” Context is MVCSContext, which is a class you can extend to get all of Strange’s wacky goodness.

当你延伸MVCSContext的时候,你可以参考如下

When extending MVCSContext, you can create your bindings right in the extended class like so:

injectionBinder.Bind<IWeapon>().To<PhaserGun>();

当你需要改变IWeapon的具体实现只需要重新映射

Now, whenever a class requires an IWeapon, the concrete class PhaserGun is provided. If you decide to change PhaserGun to SquirtCannon, you make no changes whatsoever to Spaceship or to any other class. You simple remap:

injectionBinder.Bind<IWeapon>().To<SquirtCannon>();
 
一切从简,这就是依赖注入 
Hey presto! The Spaceship now uses a SquirtCannon. All this from simply a one-word acknowledgement that this is a dependency to be injected:
class Spaceship : ISpaceship
{
    public void input(float angle, float velocity)
    {
        //do stuff here
    }
    
    [Inject] //<----- The magic word!
    public IWeapon weapon{get;set;}
}

如果有一天你发现DI不适合你的项目你可以直接将属性的[inject]标签去掉,属性还是普通的get/set

It might be of interest to note that this [Inject] attribute tag is entirely innocuous if you’re not using DI. So you can add it to your classes and then, if you someday decide this DI lark is all some terrible mistake (which it most emphatically is not), the tags in your code will make no difference to you whatsoever. Without that [Inject] tag, 'weapon' is now just a regular ol' getter/setter.

实例注入你需要做两件事

1.绑定上下文 上文中提到过了

2.从InjectionBinder实例化实例

Instantiating injectable instances

Now there is one big “take note” in all this. If you want all this injectable goodness, you need to do two things:

  1. Bind classes in the Context, which we’ve discussed, and
  2. Instantiate instances from the InjectionBinder

The second one feels unusual at first, but it’s really very straightforward. It’s just like a factory, only instead of one factory for every Type, we just go to the Injector for everything. Also, most of the time the InjectionBinder is entirely invisible. Most of us are used to constructing through constructors...

第二个感觉不同寻常,但它真的很简单。这就像一个工厂,只需要往工厂里插入各种各样的类型,我们就能注入一切。此外,大多数时候InjectionBinder是完全看不见的。我们大多数人通过构造函数用于构建

IClass myInstance = new MyClass();

所以我们需要一些训练,我必须强调,你不需要使用这种方法,因为您的实例将会被注入,你只需要我什么告诉你在这种情况下,你会倾向于编写新的MyClass()。

...so this takes a little retraining. Let me re-emphasize, most of the time you’ll not need to use this method, since your instances will come via injection. You only need what I’m about to tell you in those cases where you’d otherwise be inclined to write new MyClass().

IClass myInstance = injectionBinder.GetInstance<IClass>() as IClass;

正如你所看到的,我们从具体类型的束缚下解脱了。你的实例将预先注入其所有依赖项。这可能和你用过架构不同

As you can see, we’re still freeing ourselves from the tyranny of concrete classes. And the instance you get will come pre-injected with all its dependencies. It’s just a little different from what you’re used to.

Types of injection mapping

类型注入映射

我们可以在方方面面使用注入绑定它们都很有用,最实用的单例绑定代码如下

So we can bind injections in lots of ways, and they’re all useful. One of the most useful bindings isToSingleton. It looks like this:

injectionBinder.Bind<ISocialService>().To<TwitterService>().ToSingleton();

单例这种设计模式你或许知道,就是在你的程序域中只存在一个实例,你可能会看到这样的代码 如下

A Singleton is a design pattern you probably know. It indicates that there will only ever be one of something in an app. If you use this pattern, you might have seen a line like this:

ISocialService socialService = TwitterService.Get();

单例模式自身也有问题,实际应用中可能实例并非完全的单例,在上面的代码中,也许结果只有一个ISocialService(Twitter),但由于设计更改,明天有三个(Twitter,Facebook和google +)。TwitterService.Get的作者()不仅是依赖具体的TwitterService,她知道这是一个单例。如果改变做法,她就只有重构。

There are some problems with Singletons, most notably that sometimes they turn out to not be so singular. In the above line, for example, it may turn out that there’s only one ISocialService (Twitter) one day, but due to a design change, there are three (Twitter, Facebook and G+) tomorrow. The writer of TwitterService.Get() is not only concretely relying on TwitterService, she’s explicitly stating that she knows it’s a Singleton. If that changes, she’s got refactoring to do.

Compare this to the Singleton “Get” in Strange:

[Inject]
public ISocialService {get;set;}

当然使用Strangeioc的注入 不需要关心ISocialService 是否单例,也不需要写一个单例的实现只需要映射一个单例

Oh wait, that can’t be right. That looks exactly the same as the injection tag we saw before. Yep. That’s the point. Your class doesn’t need a TwitterService, it needs an ISocialService. And it certainly doesn’t care whether that service is a Singleton or not.

Because Strange’s dependency is only a mapping, it becomes a trivial matter in Strange to re-map our Singleton to a different service. Not only doesn’t the client have any idea which ISocialService it is, it has no idea whether the service is a Singleton or anything else. That’s as it should be. Once you start using DI, you will never write a Singleton again. You will map Singletons.

但是在我们的示例中我们不仅仅是改变服务,我们添加多个服务。那么,我们如何区分它们?这就引出了第二种类型的映射:名称注入

But in my example we’re not just changing services, we’re adding multiple services. So how do we tell them apart? This brings us to the second type of mapping: named injections.

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

命名注入与其他注入方式稍有不同。允许用注入器名称区分不同类别,满足相同的接口。通过这种方式,您可以在不同的地方,注入ISocialService得到你想要的特定版本。客户端类只需要将匹配的名称添加到注入标签内

Named injections are a tiny bit different from other injections. The name allows the injector to discriminate between different classes that satisfy the same Interface. In this way, you can inject ISocialService in different places and get the specific version you want. The client class needs the matching name added to the [Inject] tag:

[Inject (ServiceTypes.TERTIARY)] //We mapped TwitterService to TERTIARY
public ISocialService socialService{get;set;}

注入的名称可以是任何类型,但实际运用中枚举是一个不错的选择。注意,这个名称标签在你的创建一个类有各种各样的依赖项时使用(毕竟,客户期望的不仅仅是一个通用的接口),因此我们建议谨慎使用此功能。

Names can be anything, but in practice an Enum is usually a good choice. Note that this name-tagging in your classes creates a dependency of sorts (we are, after all, stating that the client expects something more than just a generic interface), so we suggest using this feature sparingly.

Sometimes you know exactly what you want to inject. Perhaps you’ve loaded a config file and you need that available in different areas around the application. This is accomplished by value mapping.

有时你想要确切地知道你的注入。也许你在正在不同的程序域中加载配置文件。这是通过值映射实现。

Configuration myConfig = loadConfiguration();
injectionBinder.Bind<IConfig>().ToValue(myConfig);

在这个例子中,myConfig将加载一些配置文件的结果。在你需要的地方使用IConfig,您将收到myConfig值。再一次,请注意,客户端类不知道是否这是一个单例,一个值,等等。它的工作是使用IConfig,而不知道它从哪里来。

In the example, myConfig would be the result of loading some configuration file. Now wherever you need an IConfig, you’ll receive the value myConfig. Again, note that the client class has no idea whatsoever whether this is a Singleton, a value, or whatever. Its job is to use IConfig, not to wonder where it comes from.

你也会遇到一个情景,在这个情景中你无法控制一个类。也许它来自一个你下载的包且你以将它编写为一个单例。你仍然可以完成与ToValue映射。就叫单例的Get()(也许在上下文)和映射结果:

You might also come across a situation where you have no control over a class. Perhaps it comes from a package you’ve downloaded and has already been written as a Singleton. You can still accomplish mapping with ToValue. Just call the Singleton's Get() (perhaps in the Context) and map the result:

TouchCommander instance = TouchCommander.Get();
injectionBinder.Bind<TouchCommander>().ToValue(instance);

当然,如果touchcommander能够始终绑定到一个接口。或者(我经常这么做),你可以创建一个接口和包在touchcommander里使用外观模式。毕竟,总有一天你会决定改变touchcommander的实现。如果你在你的应用程序有touchcommander引用,你会再次面临重构。Tips: 使用外观类(face设计模式),坚持使用接口,严格控制touchcommander的具体引用。

It would of course be better to bind it to an Interface if TouchCommander adheres to one. Or (and I do this a lot), you can create an interface and wrap TouchCommander inside a facade. After all, you might someday decide to change from TouchCommander to some other touch handling system. If you did that and had TouchCommander references throughout your app, you'd again be faced with a lot of refactoring. A facade class that adheres to an interface of your choosing saves you from this problem and keeps concrete references to TouchCommander tightly controlled.

如果你每次请求都需要一个新实例 使用如下工厂映射

Now what about if you need a new instance every time you ask for one? We accomplish this with what’s called a factory mapping:

injectionBinder.Bind<IEnemy>().To<Borg>();

This is basically the same as the ToSingleton mapping, just without the instruction ToSingleton. Whenever this injection is satisfied, you’ll get a new IEnemy, in this case mapped to the concrete class Borg. Note that we can combine these mappings so that, for example, a factory mapping can also be named:

这基本上和ToSingleton映射一样,只是没有调用ToSingleton。你会得到一个新的IEnemy,映射到具体类Borg。注意,我们可以结合这些映射,例如,一个工厂映射也可以命名为如下

injectionBinder.Bind<IEnemy>().To<Borg>().ToName(EnemyType.ADVANCED);
injectionBinder.Bind<IEnemy>().To<Romulan>().ToName(EnemyType.BASIC);

您还可以绑定多次,允许绑定是多功能的,这是一个高级的方式,一个类可以有多个接口

You can also bind multiple times, allowing a binding to be polymorphous, which is a fancy-pants way of saying that a class can have more than one interface:

injectionBinder.Bind<IHittable>().Bind<IUpdateable>().To<Romulan>();

这将允许您获得一个敌人,无论注入标记是否标志着IHittable或IUpdateable。注意,多绑定的意义不在于在这个上下文中多个To。如果结果是一个具体类型或值,你可以映射到多个接口,但是只有注入之后才有意义。

This would allow you to get an enemy regardless whether the [Inject] tag was marked IHittable or IUpdateable. Note that while multiple 'Bind's make sense, in this context multiple 'To's do not. You can map to any of multiple Interfaces, but injection only makes sense if the result is a single concrete type or value.

Some things you can do with Injectable Classes

注入类的同时你可以做一些事,先回顾下注入的使用

I’ve already mentioned how you declare injection setters in your classes. To recap, to make a property injectable, use the [Inject] attribute:

[Inject]
public ICompensator compensator{get;set;}

Or, to make it a named injection:

[Inject(CompensatorTypes.HEISENBERG)]
public ICompensator compensator{get;set;}

or, to mark it with a marker class:

[Inject(typeof(HeisenbergMarker))]
public ICompensator compensator{get;set;}
这些都是setter注入的例子,这是两种类型的注入。其他类型的注入是构造函数注入,你注入的一部分提供实际调用类的构造函数。setter注入有两个明显的缺点。首先,注入要求公共属性。这可能是你不选择使用注入的原因。使用构造函数注入可以保持私有值。其次,如果你使用setter注入你必须小心你的实际构造函数。根据定义,构造函数在setter之后执行。因此任何注入属性将不可用,直到构造函数结束。

These are all examples of setter injection, which is one of two types of injection available in Strange. The other type of injection is constructor injection, in which your injections are provided as part of the actual call to the class’s constructor. There are two notable disadvantage to setter injection. First, injecting requires making the injectable properties public. This may or may not be what you would have chosen were you not injecting. With constructor injection you can keep the private values private. Second, you have to be careful in your actual constructors if you’re using setter injection. By definition, construction has to occur before setters are set. Thus any injected properties will be unavailable until after construction. Because constructor injection provides the dependencies as constructor parameters, all values are available immediately.

类型注入的优缺点

属性注入

     优点:

         1.允许名称注入

         2.写更少的代码

         3.更灵活

    缺点:

          1.不可以在构造函数注入

          2.不得不公开一些私有的属性

构造函数注入

    优点:

         1.保持属性私有

         2.可以在构造函数注入

    缺点:

         1.不允许名称注入

         2.更多的代码量

         3.不灵活

Type of Injection

Advantages

Disadvantages

Setter

  1. Allows named injection
  2. Less code
  3. More flexible
  1. Injected dependencies not available in constructors
  2. Some properties made public that should be private

Constructor

  1. Private properties remain private
  2. Injected dependencies available in constructors
  1. Does not allow named injection
  2. More code
  3. Less flexible

In addition to [Inject] there are a couple of other attributes you should know about.

除了[Inject]这个标签之外你还应该知道的一些标签,如果你的类有多个构造函数你可以用[Construct]来标记,让StrangeIoc执行的构造函数,如果你没有加[Construct]标签的话,StrangeIoc默认执行构造函数的参数列表参数最少的函数,如果你只有一个构造函数那么相应不需要加[Construct]标签

In addition to [Inject] there are a couple of other attributes you should know about.If your class has multiple constructors, the [Construct] tag is a way to mark which one you want Strange to use. If no constructor is marked with [Construct], Strange chooses the constructor with the fewest parameters. Of course, if you have only one constructor, you needn’t use the [Construct]attribute at all.

public Spaceship()
{
	//This constructor gets called by default...
}
  
[Construct]
public Spaceship(IWeapon weapon)
{
	//...but this one is marked, so Strange will call it instead
} 

如果你选择使用getter/setter属性注入的话[PostConstruct]是个非常有用的标签,任何方法被[PostConstruct]标记,在gette/setter注入完成之后调用,它允许你在注入工作完成之后调用,他是一个安全类型不会返回空指针,如果你有多个[PostConstruct]标签,你可以在参数列表内指定执行顺序

[PostConstruct] is a useful attribute if you choose to go with setter injection. Any method marked with [PostConstruct] is called immediately following injection. This allows you to work with any injections as soon as they’re ready, safe in the knowledge that the dependencies won’t return a null pointer.

[PostConstruct]
public void PostConstruct()
{
	//Do stuff you’d normally do in a constructor
}

You can have as many [PostConstruct] methods as you like, and they can be ordered (as of v0.7).

[PostConstruct(1)]
public void PostConstructOne()
{
	//This fires first
}

[PostConstruct(2)]
public void PostConstructTwo()
{
	//This fires second
}
这篇文章建议大家可以看看 Should you use setter injection or constructor injection? 等这个系列结束或者我会翻译此文

Should you use setter injection or constructor injection? Shaun Smith, one of the authors of Robotlegs, has an excellent post on the subject here.

Warnings

There are a couple of potential gotchas to beware of with injection.

在注入使用的时候有几个潜在的陷阱

1.避免循环依赖

2.性能 StrangeIOC使用反射绑定 在你程序的敏感地带换句话说性能要求较高的地方尽量不要使用或者不使用

3.这是个显而易见的错,但请记住,如果你注入什么东西,你必须将它映射。空指针错误中大多是你创建依赖关系然后忘记实现它们。幸运的是,Strange会自动匹配相似度最高的映射(这里指自动完成映射)

1. Be careful of dependency loops. If classes inject each other, this can lead to a never-ending dependency loop. Strange armors against this to avoid bringing down your app (and will throw an InjectionException to alert you), but you should avoid doing it in the first place.

2. Injection employs reflection, which, as I’ve noted, is slow. Strange uses ReflectionBinder to minimize this problem (and delivers very formidable results), but consider carefully whether this method is appropriate for performance-sensitive code, such as your main game loop.

3. It might be obvious to say, but remember that if you inject something, you have to map it. Creating dependencies then forgetting to fulfill them results in null pointer errors. Fortunately, Strange looks for these and does its level best to help you figure out what you forgot to map and who needs it.

The reflector extension

反射延伸

老实说,你不需要知道太多关于这个延伸,除了它的存在,注入过程中处理反射。反射是在运行时分析类的过程。StrangeIoc的使用这个过程来确定注入类型。

Tips:

       (这可能是个值得注意的问题StrangeIoc框架在后期开发过程中优化缓慢。我觉得如果我缓存反射的结果反射性能可以改善,所以我写ReflectionBinder做到这一点。通过反射在反射器之前,每个类都每次时间被实例化。现在,经过这个过程每个类只有一次。结果是估计的5倍提升超过1000中等复杂实例。这是一个很好的例子,解决了核心的绑定延伸问题)。

Honestly, you don’t need to know too much about this extension, except that it’s there and that it handles Reflection during injection. Reflection is the process of analyzing classes at runtime. Strange uses this process to determine what to inject.

(It’s probably worth noting that the reflector extension was written late in development as an optimization for the slow process of Reflection. I felt that Reflection performance could be improved if I cached the result of reflecting, so I wrote ReflectionBinder to do just that. Before the reflector, every class went through Reflection every time time it was instantiated. Now it goes through that process just once per class. The result was an estimated 5x improvement over 1000 moderately complex instances. It’s a great example of extending the core Binder to solve a problem.)

可能是值得你注意的一个特点是"pre-reflect"类的能力。那就是,通过 injectionBinder 访问。您可以触发的反射,在那一刻当处理的要求最小 (比如,当玩家看一些静态的 UI) 高消耗的过程。通过 injectionBinder 访问。

One feature that might be worth your notice is the ability to “pre-reflect” classes. That is, you can trigger the expensive process of reflection at a moment when processing requirements are minimal (say, while the player is looking at some static UI). This is accessed via the injectionBinder.

第一个例子演示反射类型列表 第二个例子演示一切都被映射到了InjectionBinder

The first example demonstrates how to reflect a list of classes:

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 second example simply reflects everything already mapped to the injectionBinder;

injectionBinder.ReflectAll();
The dispatcher extension

调度器的延伸

EventDispatcher 是原始和默认调度系统。现在是一个Signals的延伸,添加了类型安全。我们建议的新的框架中支持这两个可预见的的新特性。您使用哪个是由你决定。

NB: EventDispatcher is the original and default dispatch system for Strange. There is now a Signals extension which adds type-safety to your dispatches. We recommend the new system, but plan to support both for the foreseeable future. Which package you use is up to you.

原则上,调度程序可以是我们的此次话题中经典的观察者模式的任何类。它允许客户端监听它,然后告诉那些客户某些事件发生时。我们已经实现了 EventDispatcher,将绑定一个触发器 (这可以是任何东西,但字符串或枚举通常比较管用) 对单参数或没有参数的方法,调用的时候,可能会有些问题。最后得到的参数(如果需要的话)的形式 IEvent,一个简单的值对象包含任何数据相关事件(虽然您可以编写自己的事件,满足IEvent接口,StrangeIoc规范的事件叫做TmEvent)。

In principle, a dispatcher is any class that functions as the 'subject' in a classic Observer Pattern. That is, it allows clients to listen to it, and then tells those clients whenever certain events occur. In Strange, we've implemented the EventDispatcher, which binds a trigger (which can be anything, but a string or Enum usually does the trick) to single-parameter or no-parameter methods which will react when that trigger fires. The resulting parameter (if required) will be in the form of an IEvent, a simple value object which contains any data relevant to that event (while you can write your own event that satisfies the IEvent interface, the canonical Strange event is called TmEvent).

如果你使用MVCSContext版本的StrangeIOC,有一个全局的EventDispatcher(称为“contextDispatcher”)在应用程序中自动注入周围各点发送消息,您可以在你的应用程序中使用,还有一个crossContextDispatcher用于上下文之间的通讯。

If you're using the MVCSContext version of Strange, there's a global EventDispatcher (dubbed ‘contextDispatcher’) automatically injected at various points around the app and you can use that to send messages throughout your app. There's also a crossContextDispatcher for communicating between Contexts.

EventDipatcher里你要做两个最起初的事情,分派事件与监听. 说,有相当多的方法来配置这些事件发送和接收。让我们从最简单的监听开始,直到FIRE_MISSILE被调度器调用,OnMissileFire方法才被调用

There are two basic things you can do with EventDipatcher: dispatch events and listen to them. That said, there are quite a few ways to configure just how those events are sent and received. Let’s start with the simplest form of listening.

dispatcher.AddListener("FIRE_MISSILE", onMissileFire);

This will listen to the dispatcher until an event called "FIRE_MISSILE" is dispatched, at which point a method called onMissileFire will be triggered.

我想说这样做虽然简单却不是很好,使用字符串作为Key会使代码变得很脆弱,换句话说就是,他们让代码很容易出错。在一个地方一个字符串可以改变代码的其余部分不知晓,这一定是一个灾难。用常量或者枚举会更好:

Let me suggest that while this is simple, it's not very good. Strings make code brittle, that is, they make code that breaks easily. A string in one place can change without the rest of the code knowing, and that's a recipe for disaster. A better form of the same thing would be a const...perhaps an Enum:

dispatcher.AddListener(AttackEvent.FIRE_MISSILE, onMissileFire);

You can remove the listener like so:

dispatcher.RemoveListener(AttackEvent.FIRE_MISSILE, onMissileFire);

Under the hood, AddListener and RemoveListener are just synonyms for Bind and Unbind. The AddListener/RemoveListener pair is just syntactic sugar to provide an interface with which many people are familiar. There’s also a convenience method for updating the listener based on a boolean:

AddListener与RemoveListener都是大家都很容易认知的语法糖,还有一个基于bool变量更新Listener的方法

dispatcher.UpdateListener(true, AttackEvent.FIRE_MISSILE, onMissileFire);

调用的方法的参数有无完全取决于你这个事件要承载什么样的责任

The method called can either have one argument or none, depending on whether you care about any event payload:

private void onMissileFire()
{
    //this works...
}

private void onMissileFire(IEvent evt)
{
    //...and so does this.
    Vector3 direction = evt.data as Vector3;
}

如果你想继续了解事件调用(Event Dispath)我们接下来看几个简单的例子

You’ll also want to be able to dispatch events. This is how you say "Look over here! I'm doing something cool!" There are a few ways to do this. Again, starting simple:

dispatcher.Dispatch(AttackEvent.FIRE_MISSILE);

这种调用方式会生成一个新的TmEvent并调用函数, 既然你已经不提供任何数据(这里指的是你没有Add过函数),TmEvent 字段将为空。你也可以调用调度器提供数据

This form of dispatch will generate a new TmEvent and call any listeners, but since you’ve provided no data, the data field of the TmEvent will of course be null. You can also call Dispatch and provide data:

Vector3 orientation = gameObject.transform.localRotation.eulerAngles;
dispatcher.Dispatch(AttackEvent.FIRE_MISSILE, orientation);

现在new出来的 TmEvent 将会匹配定位到 Vector3 数据。

最后,你其实可以显式创建的 TmEvent 和调用

Now the TmEvent created will have Vector3 data that matches orientation.

Finally, you can actually create the TmEvent explicitly and dispatch that:

TmEvent evt = new TmEvent(AttackEvent.FIRE_MISSILE, dispatcher, this.orientation);
dispatcher.Dispatch(evt);

使用哪种形势的调度器(Dispath)是你的代码风格体现,其实每个版本的调度器看起来都差不多

Which version of Dispatch you use is largely a stylistic choice. Every version looks the same to a listener.

The command extension

指令延伸

除了方法绑定到事件,将它绑定到命令也是MVCS中很常见的一种设计模式(命令模式),在StrangeIOC的MVCSContext版本中,CommandBinder 监听着每个调度(Dispath)的执行(当然你可以在自己的上下文中改变这种监听方式),Signals也可以绑定到命令,如果CommandBinder 找到了一个绑定,一个新的命令实例进行实例化。该命令是先注入,然后执行,最后释放。让我们先来看一个简单的命令:

In addition to binding events to methods, you can bind them to Commands. Commands are the Controllers in the classic Model-View-Controller-Service structure. In the MVCSContext version of Strange, the CommandBinder listens to every dispatch from the dispatcher (of course you can change this if you want in your own Context). Signals, described below, can also be bound to Commands. Whenever an event or Signal fires, the CommandBinder determines whether that event or Signal is bound to one or more Commands. If CommandBinder finds a binding, a new Command instance is instantiated. The Command is injected, executed, then disposed of. Let’s start by looking at a simple Command:

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);
        }
    }
}

有几件事情需要注意这个简单的例子。首先,观察我们正在使用thestrange.extensions.command.impl 命名空间,因为该命令延伸EventCommand。你不需要延伸 EventCommand,甚至命令,但您必须要实现 ICommand 接口。第二,注意你可以注入命令。这样做确实很爽,因为它意味着可以访问和交往的任何模型或服务。最后请注意通过延伸的EventCommand 我们自动获得对Dispatcher访问 (范围内的EventDispatcher 注入无处不在),所以 contextDispatcher,该应用程序,在任何地方任何侦听器可以听到 GameEvent.STARTED 我们刚触发。因为这是一个同步命令,我们只是触发和释放。 execute () 完成后,该命令将被清理掉。

There are several things to note about this simple example. First, observe that we’re using thestrange.extensions.command.impl namespace since this Command extends EventCommand. You don’t have to extend EventCommand or even Command, but your commands do have to adhere to the ICommand interface. Second, note that you can inject into commands. This is really useful, since it means that any model or service can be accessed and interacted with. Finally notice that by extending EventCommand we automatically have access to dispatcher (the EventDispatcher injected everywhere within a Context), so any listener to contextDispatcher, anywhere in the app, can hear that GameEvent.STARTED we just fired. Since this is a synchronous Command, we simply fire and forget. As soon as Execute() completes, the Command will get cleaned up.

异步调用命令如同调用一个WebService,我们有两个很简单的方法 Retain() 与 Release() 如下

But what about asynchronous Commands, like calling on a web service? We can handle these with a really simple pair of methods called Retain() and Release(). Look at this:

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的作用了,打个比方你调用了Retain该命名保持在内存中,如果你没有调用release可能会造成内存泄漏。

You can probably understand pretty much everything happening here. We pass off the SendScore request to the gameServer, which chews on it for awhile. The Command will hang around while the server does what it needs to do. By calling Retain() at the top of the Execute method, we keep the command in memory. Whenever you call Retain(), it is critically important that you call Release(), however the callback turns out. Failure to do so will result in a memory leak.

Mapping commands

命令映射

虽然技术实现上可以将我们的命令事件在任何一个地方执行,但是我们通常只在上下文中这样做,方便你和其他人找到你所想要的映射。命令映射有点像注入映射

Although technically we can map Commands to events almost anywhere, we typically do so in the Context. Doing so makes it easy to locate when you (or anyone else) needs to find what’s mapped to what. Command mapping looks a lot like injection mapping:

commandBinder.Bind(ServerEvent.POST_SCORE).To<PostScoreCommand>();

You can bind multiple Commands to a single event if you like:

你可以将多个命令绑定到一个事件上

commandBinder.Bind(GameEvent.HIT).To<DestroyEnemyCommand>().To<UpdateScoreCommand>();

And you can unbind at any time to remove a binding:

取消绑定

commandBinder.Unbind(ServerEvent.POST_SCORE);

There’s also a nice “one-off” directive for those times where you only want a Command to fire just the next time an event occurs.

仅执行一次,使用Once()声明可以保证在下次调用之前销毁这个命名

commandBinder.Bind(GameEvent.HIT).To<DestroyEnemyCommand>().Once();

By declaring Once, you ensure that the binding will be destroyed the next time the Command fires.

Sequences are a group of commands fired in order. The commands fire one-by-one either until the sequence reaches the end, or one of the commands fails. A command can call Fail() at any point, which breaks the sequence. This can be useful for setting up a chain of dependent events, for building ordered animations, or for setting a guard to determine whether or not a Command should really execute.

命令组是一连串连贯的命令,命令组中命令一个接一个执行如果其中一个失败便终止立刻调用Fail()函数 ,命令组只需要调用InSequence()函数便可以使用

Mapping a sequence simply requires the addition of the InSequence() instruction:

commandBinder.Bind(GameEvent.HIT).InSequence()
    .To<CheckLevelClearedCommand>()
    .To<EndLevelCommand>()
    .To<GameOverCommand>();

The idea behind this sequence is that a hit might indicate that a level has been cleared. So we run theCheckLevelClearedCommand. If it passes, we run EndLevelCommand. If that Command indicates we’ve reached the final level, run the GameOverCommand. Commands in a sequence execute successively, so at any point along the way, a Command can simply call Fail() to stop the execution flow.

与常规命令一样命令组(序列)也可以异步执行,如果这样做(先假定Fail()不会执行),剩下的命令会被触发,然后执行Release()函数

As with regular Commands, commands in a sequence may execute asynchronously. If they do (and presuming Fail() isn’t called), the subsequent Command will be fired as soon as Release() is invoked.

The signal extension

Signal延伸

Signal一种调度机制 — — EventDispatcher 的替代品 — — StrangeIOC的 v.0.6.0 介绍了。而 EventDispatcher 创建和调度具有单一的 dataproperty IEvent 对象,Signal挂钩回调,传递 0-4 个强类型参数。Signal比起EventDispatcher有有两个主要优点。第一,Signal调度结果中没有创建新的对象,因此GC没有必要创建很多实例。第二,更重要的是,Signal调度是类型安全的而且Signal和其映射的回调不匹配在编译时就会报错(编译器强类型检查)

Signals are a dispatch mechanism — an alternative to EventDispatcher — introduced with Strange v.0.6.0. Whereas EventDispatcher creates and dispatches IEvent objects with a single dataproperty, Signals hook to callbacks, passing 0-4 strongly-typed arguments. This has two major advantages over the EventDispatcher. First, Signal dispatch results in no new object creation, and therefore no need to GC a lot of created instances. Second, and far more importantly, Signal dispatches are type-safe and will break at compile-time if the Signals and their mapped callbacks don't match.

另一个重要的区别全局 EventDispatcher是单一的,为每个上下文 (和另一个或更多的全局上下文 CrossContextDispatcher) 。每个 '事件' 是Signal都是独立的,职责单一的结果。所以虽然EventDispatcher 是铁板一块,可能有任意数量的Signal。我们一起看下下面几个例子

Another important distinction is that while there is a single 'global' EventDispatcher for each context (and another 'even-more-global' CrossContextDispatcher) firing off Event triggers, Signals uses a different model. Each 'Event' is the result of an individual Signal tasked to some duty. So while EventDispatcher is monolithic, there may be any number of Signals. Let's show some examples.

Here are two Signals, each with one parameter:

两个Signal带一个参数

Signal<int> signalDispatchesInt = new Signal<int>();
Signal<string> signalDispatchesString = new Signal<string>();

Notice how the dispatch type of each Signal has been baked right into the instantiation. Let's build this out with some callbacks:

注意看这个回调函数

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 callback(string value)
{
    //Do something with this string
}

What's worth noticing here is that once the Signal bakes in its type, that type is a compile-time requirement of any listener to that Signal. This means the app simply won't compile if, for example, you accidentally do this:

下面演示一个被编译器报错的写法 Signal避免了程序猿的一些可能性失误

Signal<int> signalDispatchesInt = new Signal<int>();
Signal<string> signalDispatchesString = new Signal<string>();

signalDispatchesInt.AddListener(callbackString); //Oops! I attached the wrong callback to my Signal!
signalDispatchesString.AddListener(callbackInt); //Oops! I did it again! (Am I klutzy or what?!)

This makes screwing up your listeners pretty darned difficult.

Signal是类型安全的,而且是向下转型,它意味着每次赋值都是一次映射

The parameters of a Signal are type-safe and down-castable. This means that anything assignable from the parameter's Type is a legal mapping.

//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);

You can write Signals with 0-4 parameters. Signals use the Action Class as the underlying mechanism for type safety. Unity's C# implementation allows a maximum of four parameters to an Action, so that's as far as we can take you. If you require more than four parameters, consider creating a value object and sending that instead.

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>();

You can write your own Signal subclasses, of course, instead of declaring them like the inline examples above. This is especially useful in Strange, where you probably want to have some handy, human-readable names for mapping Signals within and between Contexts. Here's an example of a Signal subclass:

你当然可以按照如下方法,写Signal子类,而不是像上面在内部声明他们。这种做法在StrangeIOC中还挺有用的,在你想映射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>
    {
    }
}
Mapping Signals to Commands

将Signal映射到命令  将Signal绑定到上下文

If you want your Context to be able to bind Signals to Commands (a very good idea) you need to make one small plumbing change. If you'd rather get the full Signals experience, then add this to your Context:

protected override void addCoreComponents()
{
    base.addCoreComponents();
    injectionBinder.Unbind<ICommandBinder>();
    injectionBinder.Bind<ICommandBinder>().To<SignalCommandBinder>().ToSingleton();
}

Doing this informs Strange that we're doing away with the default CommandBinder and replacing it with the SignalCommandBinder. Thus Signals, rather than Events, will trigger Commands. Note that Strange currently supports eitherEvents or Signals mapped to Commands, but not both.

这样做会通知StrangeIOC使用SignalCommandBinder替换默认的 CommandBinder 。因此Signal而不是事件,将会触发命令。请注意StrangeIOC目前支持事件或信号映射到命令,但不是两个同时一起映射。

Having done this, Signals can now be mapped to Commands much as Events can. The basic syntax is:

commandBinder.Bind<SomeSignal>().To<SomeCommand>();

请注意它仍然是 commandBinder,我们只是取消了EventDispatcher的映射简单的连接了一个新的Signal,当然全部的映射函数都被继承下来了包括命令组(序列)与Once()等等

Note that it's still commandBinder. We simply unmapped the one that worked with EventDispatcher and hooked it up to a new one that works with Signals. Of course the full range of Command-mapping behavior is supported, including multiple Commands, sequences and mapping Once().

创建注入映射它自动映射一个Signal到一个命令

Mapping a Signal to a Command automatically creates an injection mapping which you can retrieve with the [Inject] tag, like so:

[Inject]
public ShipDestroyedSignal shipDestroyedSignal{get; set;}

Use this injection wherever necessary (always remembering to apply some common sense), including in Commands or Mediators.

ShipDestroyedCommand

为了演示Signal/命令 映射是如何进行,让我们简要地去通过一个ShipDestroyedSignal 实例映射ShipDestroyedCommand命令。我们将通过绑定Signal来启动上下文:

To clarify how Signal/Command mapping works, let's briefly go through an example by mapping theShipDestroyedSignal above to a Command. We'll start in the Context by binding the Signal:

commandBinder.Bind<ShipDestroyedSignal>().To<ShipDestroyedCommand>();

ShipMediator中我们注入Signal然后调用它

In a ShipMediator, we Inject the signal, then Dispatch it:

[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);
}

Dispatching a Signal mapped via the SignalCommandBinder results in the instantiation of a 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
        }
    }
}

正如你所看到的将Signal映射到命令的方法是非常相似的方法与事件一起使用。

两个重要注意事项: 第一,虽然信号支持相同类型的多个参数,注入不能这样做。因此不可能为具有相同类型的两个参数的信号映射到一个命令。

As you can see, the methodology for mapping Signals to Commands is very similar to the methodology used with Events.

Two important caveats: first, while Signals support multiple parameters of the same Type, injections do not. It is therefore not possible for a Signal with two parameters of the same Type to be mapped to a Command.

//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>();

这次,这可以通过映射ValueObjects绕过这个限制。

Once again, this you can work around this limitation by mapping ValueObjects instead.

The second caveat: Strange has a handy-dandy, built-in START event for kicking things off. Unbinding the EventDispatcher turns this off. It is therefore the recommended practice to override your Context's Launch method with a custom StartSignal, like so:

第二个注意事项:StrangeIoc很方便,内建了启动事件。解除了EventDispatcher的绑定,因此,建议使用自定义startsignal重写你的上下文启动方式,如下

override public void Launch()
{
    base.Launch();
    //Make sure you've mapped this to a StartCommand!
    StartSignal startSignal= (StartSignal)injectionBinder.GetInstance<StartSignal>();
    startSignal.Dispatch();
}
Mapping Signals without Commands

不使用命令映射Signal

如上所述,Signal映射到一个命令会自动创建一个映射,你可以检索注入的地方,但如果你想注入Signal并不去绑定到命令上在这种情况下,只需要使用injectionbinder,就像注入其他类:

As mentioned above, mapping a Signal to a Command automatically creates a mapping which you can retrieve by injecting else where, but what if you want to Inject a Signal without binding to a Command? In this case, simply map it using the injectionBinder, just like any other injected class:

injectionBinder.Bind<ShipDestroyedSignal>().ToSingleton();
The mediation extension

mediation(中介)延伸

MediationContext是StrangeIOC中专门Unity3D写的唯一的一部分。这是因为中介者(Mediation)都是小心地控制你的视图(GameObjects)与您的应用程序的其余部分的接口。视图是由性质极不稳定,在开发中,用它来控制内部视图的混乱,我们建议您的视图中包含至少两个不同的组件对象:视图和中介。

The MediationContext is the only part of Strange written exclusively for use with Unity3D. This is because mediation is all about carefully controlling how your views (GameObjects) interface with the rest of your app. Views are by nature highly volatile during development, and it's advisable to constrain that natural chaos to within the view classes themseves. For this reason, we suggest that your view consist of at least two distinct MonoBehaviours: View and Mediator.

View

在我们的mvc结构中视图类代表了“V”。一个视图是一个MonoBehaviour。Unity3D IDE中这个类可以附加相关GameObject 。如果是公开的components可以在IDE中正常的使用(拖拽赋值)。想要一个绿色的按钮吗?线在视图中。希望绿色按钮上有一个数字,把按钮连接在视图上吗? 。想注入模型或服务吗?等等!别干那事!为什么?

The View class represents the 'V' in our MVCS structure. A View is a MonoBehaviour that you extend to write the behavior that controls the visual (and audible) input and output that a user sees. This class can be attached in the Unity3D IDE to the relevant GameObject. If it has public components, these can be tweaked right in the IDE as normal. Want a green button? Wire it up in the View. Want the green button to have a number on it? Wire that up in the View. Want to inject a model or service? WAIT! Don’t do that! Why?

而你的视图是可注入的,把你的View直接依赖模型和服务的几乎都是不好的做法。正如我们已经说过,你的视图代码容易混乱。在下一章我们会进入我们认为使用StrangeIoc开发的最佳结构, 下面是你需要遵循的原则

1.使用拖拽的可视化组件

2.当用户与这些组件交互,使用Dispatching事件

3.暴露API允许另一个角色(Mediator)来改变这些组件的可视状态。

While your Views are injectable, it’s almost always bad practice to tie your Views directly to models and services. As we’ve said, your View code is apt to get messy and it’s worth insulating your other classes from that mess. In the next chapter we’ll get into what we consider the best structure for app development with Strange, but for now just humor us and consider the idea that your View should only be responsible for the following:

  1. Wiring up the visual components.
  2. Dispatching events when the user interacts with those components.
  3. Exposing an api which allows another actor to change the visual state of those components.

By limiting yourself to those three functions, by keeping all logic or state out of your Views, by refusing to Inject models and services, you contain the View and make your life much, much better in the long run. Trust me on this. Please.

Now, in item ‘3’ above I mention exposing an api to another actor. Who might this actor be...?

Mediator

中介

中介类是单独的 MonoBehaviour,其职责是在一般沟通关于视图和应用程序。它是一个职责单一类,这意味着其职责应该是很少很少。它允许视图的高度共享数据(注入,应用程序数据来发送和接收事件或信号)。回到上面的绿色按钮的数字。你正要在视图中注入一个服务来显示在线玩家数量。嗯,可以将服务注入中介,应为中介需要单一职业,更好的答案是分派一个请求,让命令获得服务的句柄(调用),然后发送请求。这是大量的间接寻址,但对这种间接方式的回报是干净结构的代码。

The Mediator class is a separate MonoBehaviour whose responsibility is to know about the View and about the app in general. It is a thin class, which means that its responsibilities should be very, very lean. It is allowed intimate knowledge of the View, is injectable and knows enough about the app to send and receive events or Signals. So think back to the number on the green button. You were going to inject a service into the View to display, say, the number of friends who are online. Well, you could inject the service into the Mediator, but since the Mediator is meant to be thin, a better answer would be to dispatch a request, let a Command handle the Service call, then dispatch a response. This is a lot of indirection, but the payoff for this indirection is clean, structured code.

Here’s how this might look in a 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);
        }
    }
}

Some things to note here:

注意事项

1.注入DashboardView的中介知道这个视图

2.OnRegister() 是注入后立即触发的方法。它就像一个构造函数,你可以使用它来初始化视图,并执行其他初始化过程,包括 — — 正如我们正在做的 — — 请求重要数据

3.ContextDispatcher 可以被注入任何扩展的 EventMediator,因此您总是可以在中介的上下文范围内访问事件总线

4.OnRemove() 是为清理添加过的监听 ;刚好在一个视图销毁前。请记住删除所有已经添加的侦听器。

5.在这个例子中,视图暴露了两个方法:Init() 和 updatePlayerCount(float value)。在真实的情况,你可能会有更多的API,但原则是相同的,保持中介的职责单一

  1. The injection of DashboardView is how the Mediator knows about its View.
  2. OnRegister() is the method that fires immediately after injection. It’s kind of like a constructor and you can use it to initialize the view and perform other setup processes, including — as we do here — request important data.
  3. The contextDispatcher is injected into any Mediator that extends EventMediator, so you always have access to the context-wide event bus.
  4. OnRemove() is for cleanup; it’s called just before a View is destroyed. Remember to remove any listeners you’ve added.
  5. The View in this example has exposed an api of two methods: init() and updatePlayerCount(float value). In a real situation you’d probably expect a larger api, but the principle is the same: limit the Mediator to nothing but the thin task of relaying information between the View and the rest of the app.

Binding a View to a Mediator should look pretty familiar by now:

绑定一个中介到视图上

mediationBinder.Bind<DashboardView>().To<DashboardMediator>();

A couple of other points worth noting:

还有亮点值得注意

1.没有任何的 MonoBehaviour 有资格作为一个视图

2.一个新视图对应一个新的中介 这意味着你将会由很多很多中介

  1. Not any MonoBehaviour qualifies as a View. There’s a little behind-the-scenes magic going on to allow the View to inform Strange of its existence. So either extend View, or duplicate that magic in your own code (it’s only a few lines), or perhaps create a View of your very own which extends View and which your classes extend. This latter pattern is useful, since you might want to insert debugging code that will later be accessible to all your Views.
  2. Mediation binding is instance-to-instance. Whenever a new View comes into the world anew Mediator is created to support it. So lots of Views means lots of Mediators.
The context extension

上下文延伸

多上下文允许你的项目高度模块自治

The context package puts all your various Binders under one roof, so to speak. For example, the MVCSContext includes an EventDispatcher, an InjectionBinder, a MediationBinder, and a CommandBinder. You can, as we have discussed, remap the CommandBinder to a SignalCommandBinder. The (Signal)CommandBinder listens to the EventDispatcher (or Signals). Commands and Mediators rely on Injection. The Context is where we wire up these dependencies. To create a project, you'll override Context or MVCSContext and it's in this child class that you'll write all the bindings that make your application do what it does.

It is also possible — desirable even — to have multiple Contexts. This allows your app to be highly modular. Self-standing modules can run on their own, only interfacing with other modules as needed. Thus a core game can be written as one app, a social media component written separately, and a chat app as a third, and all three can be bound together late in development, each only sharing the pieces that the other components care about.

 

写在后面的话 :这篇翻译确实花了很长的时间,自认为久到不能再拖了,在本篇中有些句子翻译起来非常拗口,如非完全必要我就一笔带过了,另外其中个别翻译肯定会有纰漏 还请多多指出。后面还有一篇实战篇需要翻译,结束后的计划是写一个开源StrangeIOC的项目,因为我实在是容忍不了那么优秀的东西在国内被埋没了。

posted @ 2015-03-14 00:48  keyle_xiao  阅读(2481)  评论(0编辑  收藏  举报