和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的項目,因為我實在是容忍不了那么優秀的東西在國內被埋沒了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM