和Keyle一塊兒學StrangeIoc – Extensions

logo
 
Strange: the IoC framework for Unity

Extensionshtml

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.react

本文例舉大部分實例代碼都在scripts/extensions/context下能夠找到,能夠參見我在Overview章末給出Strangeioc源碼地址以及在 practice 分支中整理出來的代碼。原解決方案的格式是 mono solution,我習慣用VS就順手整出來一份VS能直接打開的。最後兩個章節都是比較核心的章節固然我也不會只翻譯結論而是通篇翻譯,若是有問題的地方歡迎指出。最近家裏有點事因此本屌拖得比較久才寫完,各位看官請見諒。git

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包內綁定延伸與控制反息息相關,咱們在注入介紹中暗示過這一點,如今咱們深刻細節看看github

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.web

這是一個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:c#

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

經過實現接口咱們隔離了飛船與其餘具體組件的聯繫,面向接口編程讓咱們在編碼過程當中前進了一大步api

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

相關文章
相關標籤/搜索