Zenject框架(九)

一般指南/建議/陷阱/提示和技巧

  • 需要注入依賴項(xiàng)的物體不要用GameObject.Instantiate生成
    • 如果想要在運(yùn)行時(shí)實(shí)例化預(yù)設(shè)體并使其身上的Monobehaviour自動(dòng)被注入,我們推薦使用工廠模式。你也可以使用DiContainer調(diào)用實(shí)例化預(yù)設(shè)體的方法直接實(shí)例化預(yù)設(shè)體(詳見后續(xù)“DiContainer.Instantiate”章節(jié))。使用這些方法而不是GameObject.Instantiate將確保所有使用[Inject]屬性標(biāo)記的的字段被正確注入,以及預(yù)設(shè)體上使用[Inject]標(biāo)記的方法已經(jīng)被調(diào)用。
  • DI的最佳實(shí)踐是僅在組合的根層級(jí)上引用容器
    • 請(qǐng)注意,工廠是根層的一部分,并且可以在工廠中引用容器(這在運(yùn)行時(shí)創(chuàng)建對(duì)象所必需的)。有關(guān)詳細(xì)信息,請(qǐng)參見后續(xù)“使用工廠動(dòng)態(tài)創(chuàng)建對(duì)象”章節(jié)。
  • 不要將IInitializable,ITickableIDisposable用于動(dòng)態(tài)創(chuàng)建的對(duì)象上
    • IInitializable類型的對(duì)象只會(huì)在啟動(dòng)時(shí)在Unity的Start方法中初始化一次,如果你使用工廠創(chuàng)建IInitializable類型的對(duì)象,Initialize()方法不會(huì)被調(diào)用,這種情況下,你應(yīng)該在創(chuàng)建對(duì)象后使用[Inject]標(biāo)記的方法明確的調(diào)用Initialize()。
    • 這同樣適用于ITickable and IDisposable。除非它們是啟動(dòng)時(shí)創(chuàng)建的原始對(duì)象圖的一部分,否則它們不會(huì)執(zhí)行任何操作。
    • 如果動(dòng)態(tài)創(chuàng)建的對(duì)象包含Update()方法,最好在高級(jí)別的類中手動(dòng)調(diào)用Update()方法,比如通常會(huì)有的Manager類。對(duì)于動(dòng)態(tài)的對(duì)象,如果你更喜歡使用ITickable,你也可以在TickManager中聲明依賴項(xiàng)然后明確的添加或者刪除它們。
  • 使用多個(gè)構(gòu)造函數(shù)
    • 你可以使用多個(gè)構(gòu)造函數(shù),但必須使用[Inject]屬性修飾其中一個(gè)以確保Zenject知道要使用哪一個(gè)。如果存在多個(gè)構(gòu)造函數(shù)但均沒有使用[Inject]標(biāo)記,zenject將默認(rèn)使用參數(shù)最少的構(gòu)造函數(shù)。
  • 延遲實(shí)例化對(duì)象和對(duì)象圖
    • Zenject不會(huì)立即實(shí)例化所有在安裝器中綁定的對(duì)象,只會(huì)實(shí)例化使用[NonLazy]標(biāo)記的對(duì)象。其他的對(duì)象只會(huì)在需要時(shí)生成。 所有的[NonLazy]對(duì)象以及它們的依賴項(xiàng)構(gòu)成了程序的“初始對(duì)象圖”。請(qǐng)注意,這會(huì)自動(dòng)包含所有實(shí)現(xiàn)了IInitializable, ITickable, IDisposable,等的類型。因此,如果您有一個(gè)綁定未被創(chuàng)建是因?yàn)槌跏紝?duì)象圖中沒有任何內(nèi)容引用它,您可以通過(guò)添加NonLazy到綁定來(lái)使其顯式化
  • 僅將綁定命令的使用限制為“組合根”。 換句話說(shuō),就是在安裝階段完成之后不要調(diào)用Container.Bind, Container.Rebind, 和 Container.Unbind,這很重要,因?yàn)樵诎惭b完成后會(huì)立即構(gòu)建應(yīng)用程序的初始對(duì)象圖,并且需要訪問(wèn)完整的綁定集。
  • 事情發(fā)生的順序是錯(cuò)誤的,比如注入發(fā)生的太遲,或者Initialize()沒有在正確的時(shí)間被調(diào)用等等
    • 這可能是因?yàn)閆enject的 ProjectKernel類或SceneKernel類或SceneContext類執(zhí)行順序不正確。這幾個(gè)類應(yīng)始終最早或接近最早被執(zhí)行。這應(yīng)該已經(jīng)默認(rèn)設(shè)置好的(因?yàn)榇嗽O(shè)置包含在這些類的cs.meta文件中)。但是,如果您需要自己編譯Zenject或者有一個(gè)需要確認(rèn)的特殊的配置,您可以通過(guò)轉(zhuǎn)到“Edit -> Project Settings -> Script Execution Order”并確認(rèn)這些類位于頂部。

游戲物體綁定方法(Game Object Bind Methods)

對(duì)于創(chuàng)建新游戲?qū)ο蟮慕壎ǎɡ?code>FromComponentInNewPrefab,FromNewComponentOnNewGameObject等),還有兩個(gè)額外的綁定方法。

  • WithGameObjectName =用于提供與此綁定關(guān)聯(lián)的新游戲?qū)ο蟮拿Q。
Container.Bind<Foo>().FromComponentInNewPrefabResource("Some/Path/Foo").WithGameObjectName("Foo1");
Container.Bind<Foo>().FromNewComponentOnNewGameObject().WithGameObjectName("Foo1");
  • UnderTransformGroup(string) =用于放置新游戲?qū)ο蟮膖ransform group的名稱。這對(duì)于可用于創(chuàng)建很多預(yù)制體副本的工廠特別有用,因此讓它們自動(dòng)在場(chǎng)景的層級(jí)面板中組合是很好的。
Container.BindFactory<Bullet, Bullet.Factory>()
    .FromComponentInNewPrefab(BulletPrefab)
    .UnderTransformGroup("Bullets");
  • UnderTransform(Transform) = 放置新游戲?qū)ο蟮膶?shí)際transform
Container.BindFactory<Bullet, Bullet.Factory>()
    .FromComponentInNewPrefab(BulletPrefab)
    .UnderTransform(BulletTransform);
  • UnderTransform(Method) = 提供可用的transform的方法
Container.BindFactory<Foo, Foo.Factory>()
    .FromComponentInNewGameObject()
    .UnderTransform(GetParent);

Transform GetParent(InjectContext context)
{
    if (context.ObjectInstance is Component)
    {
        return ((Component)context.ObjectInstance).transform;
    }

    return null;
}

此示例會(huì)自動(dòng)將Foo游戲?qū)ο笾糜谄渥⑷氲挠螒驅(qū)ο笙?,除非被注入的?duì)象不是MonoBehaviour,在這種情況下,F(xiàn)oo游戲?qū)ο髸?huì)保留在場(chǎng)景層次面板的根部。

可選的綁定(Optional Binding)

您可以將某些依賴項(xiàng)聲明為可選,如下所示:

public class Bar
{
    public Bar(
        [InjectOptional]
        IFoo foo)
    {
        ...
    }
}
...

// You can comment this out and it will still work
Container.Bind<IFoo>().AsSingle();

或者加一個(gè)標(biāo)識(shí)符:

public class Bar
{
    public Bar(
        [Inject(Optional = true, Id = "foo1")]
        IFoo foo)
    {
        ...
    }
}

如果未在任何安裝器中綁定可選依賴項(xiàng),則它將被注入為null。
如果依賴關(guān)系是基本類型(例如int, float,struct),那么它將被注入其默認(rèn)值(例如,0用于整數(shù))。
您還可以使用標(biāo)準(zhǔn)C#方式分配顯式默認(rèn)值,例如:

public class Bar
{
    public Bar(int foo = 5)
    {
        ...
    }
}
...

// Can comment this out and 5 will be used instead
Container.BindInstance(1);

另請(qǐng)注意,[InjectOptional]在這種情況下不需要的,因?yàn)槟J(rèn)值指明了值。
或者,您可以將基本參數(shù)定義為可空,并根據(jù)是否提供值執(zhí)行邏輯,例如:

public class Bar
{
    int _foo;

    public Bar(
        [InjectOptional]
        int? foo)
    {
        if (foo == null)
        {
            // 如果沒有指定則使用5
            _foo = 5;
        }
        else
        {
            _foo = foo.Value;
        }
    }
}

...

// Can comment this out and it will use 5 instead
Container.BindInstance(1);

條件綁定(Conditional Bindings)

在許多情況下,您需要限制注入給定依賴項(xiàng)的位置。您可以使用以下語(yǔ)法執(zhí)行此操作:

Container.Bind<IFoo>().To<Foo1>().AsSingle().WhenInjectedInto<Bar1>();
Container.Bind<IFoo>().To<Foo2>().AsSingle().WhenInjectedInto<Bar2>();

請(qǐng)注意,WhenInjectedInto是以下的簡(jiǎn)寫,下面的語(yǔ)法使用了更通用的When()方法:

Container.Bind<IFoo>().To<Foo>().AsSingle().When(context => context.ObjectType == typeof(Bar));

InjectContext類(在上面被作為context參數(shù)進(jìn)行傳遞)包含您可以作為條件使用信息,如下:

  • Type ObjectType

列表綁定(List Bindings)

當(dāng)Zenject找到同一類型的多個(gè)綁定時(shí),它會(huì)將其解釋為列表。因此,在下面的示例代碼中,Bar將獲得包含新實(shí)例Foo1,F(xiàn)oo2,F(xiàn)oo3的列表:

// In an installer somewhere
Container.Bind<IFoo>().To<Foo1>().AsSingle();
Container.Bind<IFoo>().To<Foo2>().AsSingle();
Container.Bind<IFoo>().To<Foo3>().AsSingle();

...

public class Bar
{
    public Bar(List<IFoo> foos)
    {
    }
}

列表的順序與使用Bind方法添加順序的順序相同。唯一的例外是當(dāng)您使用子容器時(shí),因?yàn)樵谶@種情況下,列表將首先由關(guān)聯(lián)的子容器排序,第一組實(shí)例從最底層的子容器中獲取,然后是父級(jí),然后是祖父級(jí),等等。

使用工程長(zhǎng)下文的全局綁定(Global Bindings Using Project Context)

如果有一些依賴項(xiàng)需要在所有的場(chǎng)景中長(zhǎng)久保存,該怎么辦?在Zenject中,您可以通過(guò)向ProjectContext對(duì)象添加安裝器來(lái)完成此操作。

為此,首先需要為ProjectContext創(chuàng)建一個(gè)預(yù)制體,然后您可以為其添加安裝器。您可以通過(guò)選擇Edit -> Zenject -> Create Project Context菜單項(xiàng)輕松完成此操作。然后,您應(yīng)該可以在Assets/Resources看到名為“ProjectContext”的新資產(chǎn)?;蛘撸梢栽诠こ堂姘逯杏覔裟硞€(gè)位置并選擇Create -> Zenject -> ProjectContext。

如果單擊此項(xiàng),可以看到它與SceneContext的檢視面板幾乎完全相同。配置此預(yù)制體的最簡(jiǎn)單方法是暫時(shí)將其添加到場(chǎng)景中,向其中添加安裝器,然后單擊“apply”將其保存回預(yù)制體,然后再?gòu)膱?chǎng)景中刪除它。除了安裝器,您還可以直接將自己的自定義MonoBehaviour類添加到ProjectContext對(duì)象。

然后,當(dāng)您啟動(dòng)任何包含SceneContext的任何場(chǎng)景時(shí),ProjectContext對(duì)象都會(huì)首先初始化。您在此處添加的所有安裝器都將被執(zhí)行,在安裝器中添加的綁定將可用于項(xiàng)目中的所有場(chǎng)景。該ProjectContext游戲?qū)ο蟊辉O(shè)置為DontDestroyOnLoad因此更改場(chǎng)景時(shí)也不會(huì)被銷毀。

另請(qǐng)注意,這只發(fā)生一次。如果從第一個(gè)場(chǎng)景加載另一個(gè)場(chǎng)景,ProjectContext則不會(huì)再次調(diào)用,并且之前添加的綁定將保留在新場(chǎng)景中。您可以使用與場(chǎng)景安裝器相同的方式在項(xiàng)目上下文安裝器中聲明ITickable/ IInitializable/ IDisposable對(duì)象,其結(jié)果是IInitializable.Initialize只在每次運(yùn)行時(shí)執(zhí)行一次,IDisposable.Dispose只在應(yīng)用程序完全停止時(shí)調(diào)用一次。

您添加到全局安裝器的所有綁定都可用于每個(gè)場(chǎng)景中的所有類的原因是因?yàn)槊總€(gè)場(chǎng)景中容器都會(huì)使用的ProjectContext的容器作為父對(duì)象 。有關(guān)嵌套容器的更多信息,請(qǐng)參見后續(xù)。

ProjectContext是一個(gè)放置跨場(chǎng)景保留的對(duì)象的非常方便的地方。但是,它對(duì)每個(gè)場(chǎng)景都是完全全局的這一事實(shí)可能導(dǎo)致一些意想不到的行為。例如,這意味著即使您編寫了一個(gè)使用Zenject框架的簡(jiǎn)單測(cè)試場(chǎng)景,它也會(huì)加載ProjectContext,這可能不是您想要。為了解決這些問(wèn)題,通常最好使用Scene Parenting,因?yàn)檫@種方法允許您根據(jù)哪些場(chǎng)景繼承相同的公共綁定來(lái)選擇。有關(guān)該方法的更多詳細(xì)信息,請(qǐng)參見“Scene Parenting Using Contract Names”章節(jié)

另請(qǐng)注意,默認(rèn)情況下,在ProjectContext中實(shí)例化的任何游戲?qū)ο蠖寄J(rèn)成為其子級(jí)。如果您希望將每個(gè)新實(shí)例化的對(duì)象放置在場(chǎng)景層次面板的的根目錄中(但仍標(biāo)記為DontDestroyOnLoad),則可以通過(guò)在ProjectContext的檢視面板中取消選中“Parent New Objects Under Context”。

標(biāo)識(shí)符(Identifiers)

如果您需要為相同類型設(shè)置不同的綁定,而不僅僅是將其保存在列表中,你可以為綁定添加標(biāo)識(shí)符。例如:

Container.Bind<IFoo>().WithId("foo").To<Foo1>().AsSingle();
Container.Bind<IFoo>().To<Foo2>().AsSingle();

...

public class Bar1
{
    [Inject(Id = "foo")]
    IFoo _foo;
}

public class Bar2
{
    [Inject]
    IFoo _foo;
}

在該例中,Bar1類將會(huì)得到Foo1的實(shí)例,而Bar2類會(huì)得到IFoo綁定的默認(rèn)版本也就是Foo2的實(shí)例。
另請(qǐng)注意,您也可以對(duì)構(gòu)造函數(shù)/注入方法參數(shù)執(zhí)行相同的操作:

public class Bar
{
    Foo _foo;

    public Bar(
        [Inject(Id = "foo")] 
        Foo foo)
    {
    }
}

在很多情況下,標(biāo)識(shí)符是字符類型的,但實(shí)際上可以是任意類型的,在下面的例子中使用了枚舉作為標(biāo)識(shí)符:

enum Cameras
{
    Main,
    Player,
}

Container.Bind<Camera>().WithId(Cameras.Main).FromInstance(MyMainCamera);
Container.Bind<Camera>().WithId(Cameras.Player).FromInstance(MyPlayerCamera);

你也可以使用自定義的類型,只要它們實(shí)現(xiàn)Equals運(yùn)算符即可。

可編輯對(duì)象安裝器(Scriptable Object Installer)

自定義的安裝器除了可以派生自MonoInstaller或 Installer,也可以派生自ScriptableObjectInstaller。這最常用于存儲(chǔ)游戲設(shè)置。這種方法具有以下優(yōu)點(diǎn):

  • 停止運(yùn)行后,對(duì)安裝器屬性所做的任何更改都將保留。這在運(yùn)行時(shí)調(diào)整參數(shù)非常有用。對(duì)于場(chǎng)景中的其他類型的安裝器以及MonoBehaviour,在停止運(yùn)行后,所有在運(yùn)行時(shí)對(duì)屬性的修改都將消失。但是,有一個(gè)“問(wèn)題”需要注意:代碼中對(duì)這些設(shè)置的任何更改也將被持久保存(與在MonoInstaller上的設(shè)置不同)。因此,如果您使用這種方式,應(yīng)該將所有設(shè)置對(duì)象視為只讀,以避免這種情況發(fā)生。

  • 您可以非常輕松地交換同一安裝器的多個(gè)實(shí)例。例如下面的示例,您可能有一個(gè)名為GameSettingsEasyGameSettingsInstaller的實(shí)例,以及另一個(gè)名為GameSettingsHard的實(shí)例,等等。

例子:

  • 打開Unity
  • 右鍵單擊工程面板中的某個(gè)位置并選擇 Create -> Zenject -> ScriptableObjectInstaller
  • 將其命名為GameSettingsInstaller
  • 再次右鍵單擊同一位置
  • 選擇新添加的菜單項(xiàng) Create -> Installers -> GameSettingsInstaller
  • 按照此處列出的設(shè)置方法,您可以將其替換為以下內(nèi)容:
public class GameSettings : ScriptableObjectInstaller
{
    public Player.Settings Player;
    public SomethingElse.Settings SomethingElse;
    // ... etc.

    public override void InstallBindings()
    {
        Container.BindInstances(Player, SomethingElse, etc.);
    }
}

public class Player : ITickable
{
    readonly Settings _settings;
    Vector3 _position;

    public Player(Settings settings)
    {
        _settings = settings;
    }

    public void Tick()
    {
        _position += Vector3.forward * _settings.Speed;
    }

    [Serializable]
    public class Settings
    {
        public float Speed;
    }
}
  • 現(xiàn)在,您應(yīng)該能夠運(yùn)行游戲時(shí)修改GameSettingsInstaller上的速度值,并保存該更改
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Listen to your story, Guard your sincere wishes! (一) 聆聽一段...
    悅呈美媒閱讀 493評(píng)論 0 0
  • 寫這篇文章是因?yàn)槲业艿軇倓偨o我打電話,哭訴他的煩惱,恰逢另外一個(gè)小伙伴也正在向我傾訴,有感而發(fā),隨心而寫。 弟弟今...
    我是張艷你是誰(shuí)閱讀 441評(píng)論 2 0
  • 多少年已去滄桑磨礪歲月也被憔悴了色彩 時(shí)而還會(huì)想起你想起我們的花季恰似純美的夢(mèng)深藏記憶里 當(dāng)思緒深深拉進(jìn)回憶如尋覓...
    _賈立松_閱讀 389評(píng)論 0 1
  • 軒: 抬頭看看教室里的倒計(jì)時(shí),在校時(shí)間只剩下了六天,忽然感覺到了時(shí)光的匆匆。 特別懷念八年級(jí)的時(shí)光。那時(shí)候,你是多...
    玫蘭妮閱讀 260評(píng)論 0 0

友情鏈接更多精彩內(nèi)容