メンテナンス性の高いコードを開発するためのアイデアです。ScenarioFlowはDIにもとづいて設計されており、拡張性が高いシステムとなっています。DIについての理解を深めることにより、ScenarioFlowの力をより大きく引き出すことができるでしょう。一般的に、ソフトウェアは一度リリースされたらそれで終了ではなく、リリース後もバグを修正したり、既存の機能を変更したり、新たな機能を追加したりといったメンテナンスを行います。そのような作業を効率よく行うにあたっては、コードのメンテナンス性が重要です。将来的なコードの改変を想定し、その要求に対応するために良く設計されたコードは変更に対して強く、将来のメンテナンスにかかるコストを減らすことができます。コードのメンテナンス性を高めるための優れた方法が、疎結合なコードを設計することです。疎結合とは、システム全体におけるモジュール間が弱い依存関係でつながっていることであり、モジュールそれぞれの独立性が高いことからメンテナンス性が高まります。疎結合な設計を身近なもので例えると、USBが該当します。USBは、PCと周辺機器をつなぐための規格の一つです。USB規格に準拠してさえいれば、キーボードでも、マウスでも、USBメモリでも、同じ端子を使って様々な機器をPCに接続することができるという利点があります。PCとUSBでつながれる周辺機器は、完全に独立しています。DIは、疎結合なコードを設計するための指針です。「実装に対してではなく、インターフェースに対してプログラムする」ことを原則として、互いに依存性の低いコードを設計することができます。DIをうまく適用できた場合、USBによって独立性が高まるPCとその周辺機器の関係のように、「使うコード」と「使われるコード」が互いに独立して存在し、ある規格に則る限り、「使われるコード」の取り換えがきくようになります。DIに基づいたプログラミングを理解することによって、変更に強いコードを開発することができるでしょう。密結合なコード密結合なコードは、ノートPCに搭載されたキーボードのようなものです。ノートPCのキーボードは、それ専用に設計されているため、小さな領域に対してコンパクトにぴったりと収まります。持ち運びのしやすさという観点では、それが利点となるでしょう。このような設計にはいくつかの欠点もあります。一つは、メンテナンス性が低いということです。このようなキーボードが故障し、キーボードのみを新品に交換する場合を考えると、それは容易ではありません。機器を分解するための道具や機器についての知識が必要ですし、実際に機器を分解し、交換し、元に戻すのには時間と手間がかかります。一つは、互換性が低いということです。あるノートPCのキーボードを、メーカーが異なる別のノートPCに移植できるでしょうか。それは、ほぼ不可能といっていいでしょう。ノートPCのキーボード疎結合なコード対して疎結合なコードは、USBでキーボードをPCに接続するようなものです。この場合、キーボードが故障して交換したければ、古いキーボードのUSB接続を外し、新たなキーボードをUSBで接続するだけです。どのメーカーによって作られたキーボードなのかも関係ありません。LEDの有無に関わらず、マクロ機能、その他便利機能の有無に関わらず、USBコネクタがついてさえいれば、どんなキーボードでも使うことができます。USB接続のキーボード密結合と疎結合の違い疎結合なコードと密結合なコードの間にある違いの中で、最も重要なのは「容易に交換できる」ということです。一つ目の例では、キーボードはある型のノートPC専用に設計されていたために交換が困難ですが、二つ目の例では、USBという、デバイスが従うべき共通の規格があるおかげで交換が容易にできます。DIを適用するコードでは、USBの役割を担うのはインターフェースです。設計の中で、もしクラスが外部の機能を必要とするのであれば、別のクラスを直接使用するのではなく、必要な機能を保証するインターフェースを定義してそれを使用するようにします。必要な機能を持つクラスにはそのインターフェースを実装させるようにします。すれば、実際に使用するクラスを、同一のインターフェースを実装するものの中から好きに選択し、後から何度でも簡単に交換することができるようになります。world!“を表示するためのプログラムを、DIを使って書いてみましょう。コードまずは、以下の4つのクラスを用意します。SampleManagerのみ、MonoBehaviourオブジェクトです。SampleManagerを適当なオブジェクトにアタッチし、実行してください。world!“が表示されます。作成したクラスは上図のような依存関係となっています。DIを採用したことによる利益は後に学びますが、ここでは、DIを取り入れたコードの特徴について、以下の2点を確認しておきましょう。Someoneクラスが、ITextWriterインターフェースに依存しているSomeoneクラスは、ITextWriterインターフェースの実装をコンストラクターで受け取っているこれらを抽象化すれば、次のようになります。あるクラスが、あるインターフェースに依存しているインターフェースの実装は、クラスの外側から渡されるここから、DIとは何なのか、その正体が導かれます。「実装に対してではなく、インターフェースに対してプログラムする」ことがDIの原則であると前のセクションで述べました。Someoneクラスは、ConsoleTextWriterクラスを直接的に使用するのではなく、ITextWriterインターフェースを介して間接的に使用しています。言えば、SomeoneクラスはConsoleTextWriterクラスの存在を一切知りません。Someoneクラスがコンストラクターを介してConsoleTextWriterクラスのインスタンスを受け取るとき、そのインスタンスはあくまでITextWriterインターフェースの実装として渡されるからです。これが、実装(=クラス)に対してではなく、インターフェースに対してプログラムするということです。DIのポイントは、あるクラスがあるインターフェースと依存関係(Dependency)を持つこと、そして、その依存が後に外部から渡される、つまり注入(Injection)されることです。コンストラクター・インジェクションSomeoneクラスは、ITextWriterインターフェースの実装をコンストラクターで受け取っています。実は、DIにおいて、あるクラスが依存を受け取る場所はコンストラクター以外にもあるのですが、特にコンストラクターで渡す手法をコンストラクター・インジェクションと呼んでいます。ようにガード句を置き、受け取った実装がnullでないことを確認するようにします。受け取った実装を読み取り専用のフィールド変数に保持し、クラス内のメソッドで使用できるようにします。このフィールド変数は読み取り専用とし、後に変更できないようにします。コンストラクター・インジェクションは、いくつかあるインジェクションの方式の中で最もよく使われ、また最も理想的な方式です。特別な理由がない限りは、基本的にこの方式を使用します。例だと、DIから得られる利益が見えにくく、単にコードを冗長にしただけだと思われるかもしれません。実際、この例では完全にDIの適用は無駄です。実際のアプリケーションで、DIはその効力を発揮します。ここからのセクションで、より実践的な例とともに、DIの利点を見ていきます。DIの適用ここでは、DIにより疎結合なコードを書くことの利点を見ていきます。あるシナリオに合わせて、密結合なコードを作成します。そこでそのコードの問題点を指摘した後、コードにDIを適用し、疎結合なコードとして書き直します。最後に、DIを適用したことによる効果を確認します。ログインボーナスの判定プログラム今回は、モバイルゲームにおいて、ログインしたプレイヤーに対してある条件を満たすときにボーナスを付与するという想定でプログラムを書いてみたいと思います。要求される処理は次の通りです。ログイン時、現在の日付を取得する日付によって以下のアイテムをプレイヤーに配布する5日:コイン10枚15日:ダイヤ1つ25日:コイン20枚、ダイヤ3つ密結合なコードまずは以下のコードを用意し、SampleManager.csがシーン上のオブジェクトにアタッチされていることを確認して実行します。クラスの依存関係も、合わせて示しておきます。実行したその時の日付によって結果は異なり、以下のようになります。ボーナスなし5日のボーナス15日のボーナス25日のボーナス密結合なコードの問題点ここでは、前のセクションで作成したシステムについて、いくつかの観点から問題点を指摘します。不確定性と単一テストDateBasedLoginBonusProviderクラスの単一テストを行いたいとしましょう。このテストでは、取得された日付によって、正しくログインボーナスが付与されるかどうかを確かめなければなりません。現時点のコードで取得されるのは、そのコードが実行された時点での日付です。これでは、テストを実行する、その時の日付に対するテストしか実施することができません。ログインボーナスのすべてのパターンに対してテストを行うためには、現在のコードではクラスの一部をテストのたびに書き換える必要があります。};}C#この方法は、極めて非効率的です。クラスの単一テストを自動化できず、テストケースごとにコードを少しだけ書き換えるという面倒な作業を、テストをやり直す必要が生じたたびに強いられることになるからです。密結合なコードに不確定性を孕んだ処理、例えば現在時刻の取得や乱数が絡む処理などがあると、このように単一テストを行うのが困難になります。クラスの開発を担当しているとしましょう。クラスの開発が早く終了し、何らかの事情でDateBasedLoginBonusProviderクラスの開発が遅れているとすると、何が起こるでしょうか。クラスを使用しています。そのため、前者のテストは後者が完成するまで実施することができません。これは、チームによるシステムの並行開発の難しさを意味します。LoginService.csの一部。bonusProvider.GetLoginBonus();C#今回私たちが作成したものは単純なので、それほどこれが問題だとは想像できないかもしれません。その場合は、2つのクラスがもっと複雑な処理を行う場合を考えてみましょう。LoginServiceクラスでは、ログインボーナスを取得した後に取得したアイテムの画像を画面に表示させるための指示を出さなければいけなかったり、DateBasedLoginBonusProviderクラスでは、プレイヤーの端末から時刻を取得するのではなく、不正を防止するために近くのサーバーにネットワーク経由でアクセスして、日付の情報を取得しなければならなかったり、といったケースです。重要なのは、それぞれのクラスの開発には時間がかかり、また、何らかのトラブルにより予期しない遅延が起こる可能性も考えられるようなケースを想定しているということです。このようなケースで、一方のクラスのテストを行うためにはそのクラスが使用しているもう一方のクラスの開発が終了している必要があるというのは、非常に非効率的な状況です。テストを行いたいクラスが依存しているクラスの開発が完了していない場合、開発が完了するまで待機を強いられ、その時間が無駄だからです。理想としては、それぞれのクラスは独立に、並行に開発ができるべきです。現在、ログインボーナスの判定の際には、ログイン時の日付が特定の日付に合致するかどうかを確かめています。モバイルゲームの何らかのイベントで、一時的にどの日付でも一定のログインボーナスを付与したい状況を考えてみましょう。すべての日付でボーナスを付与するための新たなクラスを実装する必要があります。密結合なコードでは、古いクラスを使用していたクラスを書き換える必要があります。新たにEventLoginBonusProviderクラスを作成した場合、LoginServiceクラスを書き換えなければなりません。}}C#問題なのは、上位のモジュールが、下位のモジュールの変更に振り回されるということです。下位のモジュールに変更が加わるたびに、上位のモジュールにも変更を加えなければならず、再コンパイルが必要になります。今回は、上位のモジュールがLoginServiceクラス、下位のモジュールがDateBasedLoginBonusProviderクラスとEventLoginBonusProviderクラスです。各モジュールは異なるプロジェクトに属し、加えて、LoginServiceクラスが属するプロジェクトはサイズが非常に大きいと仮定しましょう。すると、ログインボーナスの付与方法を変更するたびに、LoginServiceクラスに変更を加えることになり、結果、大きなプロジェクトの再コンパイルとファイルの大規模な置き換えが必要になることになります。モバイルゲームにおけるログインボーナスの付与方法など、頻繁に変更されることが予想される機能に関してこのような状況になることは避けたいところです。再利用可能性の低下ログインボーナスについて、あるイベントで一時的に、通常のボーナスに加えて追加のボーナスを配布することを考えましょう。これは、例えば新しいクラスを作成し、LoginServiceクラスを書き換えることで実現できます。通常のボーナスに追加のボーナスを付与するクラス。}}C#ExCoinLoginBonusProviderクラスでは、通常通り、日付によるボーナス配布に加え、どの日付でもコインが10枚配布されるようになっています。問題は、「通常のボーナス配布」をハードコーディングしていることです。通常のボーナス配布に関する処理は、すでにDateBasedLoginBonusProviderクラスに記述されているわけですが、その論理を再び別のクラスに書いているわけです。通常配布されるボーナスが何なのかは、将来変わる可能性があります。今の実装方法では、将来変更があったときに、2つのクラスに変更を加える必要があります。多くの種類の、通常配布ボーナスに依存するクラスがあれば、それらすべてのクラスに変更を加える必要があります。このように、密結合なコードでは、すでに完成している論理を再利用することが難しくなります。論理の再利用をしないということは、変更に対するコストが大きくなることを意味します。疎結合なコードここまでで、ログインボーナスを付与するシステムを密結合なコードで作成し、その欠点について確認しました。ここからは、システムを疎結合なコードで書き直し、その利点についてみていきます。このセクションでは、まずはコードの書き直しを行います。以下のクラスを用意しましょう。これらのクラスの依存関係も、合わせて示します。同様の結果になります。疎結合なコードによる問題の解決システムを疎結合なコードで書き直したので、密結合なコードで生じていた問題がどのように解決されるのかを見ていきます。テスト用オブジェクトを用いた単体テストDIを適用した疎結合なコードでは、テスト用のオブジェクトを作成することで、時刻や乱数による不確定性や、下位モジュールの開発の遅延を乗り越えて単体テストを実施することができます。現在時刻に基づいてログインボーナスを決定する、DateBasedLoginBonusProviderのテストコードは次のように書けます。Runnerを使用しています。Runnerによる自動テストこのような単体テストが実施できるのは、DateBasedLoginBonusProviderクラスが依存しているのが、DateTimeProviderクラスではなく、IDateTimeProviderインターフェースだからです。そのインターフェースの実装はコンストラクターで渡すことになっているので、そのインターフェースを実装するクラスであれば、何でも渡すことができます。本来渡すはずであるDateTimeProviderクラスは、プログラム実行時の実行時間を返す、不確定性を持った実装なので、単体テストの自動化には向きません。そこで、今回の例ではDateTimeProviderStubというクラスを作成し、DateBasedLoginBonusProviderクラスに渡されるDateTimeを自由に設定できるようにしています。このように、要求されているインターフェースを実装する、テスト用のオブジェクトを作成することで、不確定性を持つ下位モジュールを使用するクラスの単体テストを実施することができます。加えて重要なのは、各クラスがインターフェースを仲介としてつながることで独立性が高まり、完全な並行開発が可能になるということです。今回は、DateBasedLoginBonusProviderクラスのテストの例を確認しましたが、LoginServiceクラスのテストでも同じようなことをします。LoginServiceクラスはILoginBonusProviderインターフェースに依存しているので、DateBasedLoginBonusProviderクラスに限らず、そのインターフェースを実装するクラスなら何でも渡すことができます。そのため、やはりテスト用のクラスを作成することでDateBasedLoginBonusProviderクラスと関係なしに、LoginServiceクラスのテストを行うことができます。2つのクラスの間に直接的な関係はなく、各クラスの開発は独立に進めることができます。まとめると、DIを適用した疎結合なコードでは、クラスどうしをインターフェースを介して間接的につなげることによって、テストをしたいクラスに対して、テストのためだけに作られた都合の良い実装を渡すことができるようになります。結果、下位モジュールの不確定性に関係なく様々なテストケースを自由に試すことができ、また下位モジュールの開発進度に関係なく、上位モジュールのテストを独立して行うことができます。スタブ今回の例で作成したような、「一定の値を返すテスト用のオブジェクト」を、スタブと呼びます。テスト用のオブジェクトには、他にモック、スパイ、フェイクなどがあります。Double)と呼びます。Doubleは影武者を意味します。テストダブルは、テスト対象の依存先に不確定性があったり、依存先の開発のコストが高かったりする場合に有効です。同じインターフェースに対する実装の置き換え密結合なコードでは、下位モジュールの変更が上位モジュールに影響を及ぼすという問題がありました。あるイベントでログインボーナスの付与に関する論理を変更しようとして新たなクラスを作成すると、その論理を使用するLoginServiceクラスにも影響が及びます。疎結合なコードでは、クラス同士はインターフェースでつながっています。そのため同じインターフェースを実装しているクラスであれば、上位のモジュールに変更を加えることをなく、自由に置き換えることができます。クラスに変更を加えることでそれが実現できます。ILoginBonusProviderインターフェースの実装としてもともとDataBasedLoginBonusProviderクラスがLoginServiceクラスにインジェクトされていたのに対し、下のコードでは、EventLoginBonusProviderクラスが代わりにインジェクトされています。ポイントは、LoginServiceクラスが使用する実装を変更したい際に、LoginServiceクラス自体には一切の変更を加える必要がないということです。これは、下位のモジュールの変更が上位のモジュールに影響を及ぼさないことを意味します。}}C#実行結果コンポジション・ルート依存関係の図にみられるように、SampleManagerクラスは、自分自身以外のすべてのクラスのインスタンスを作成し、各オブジェクトを結びつける役割を担っています。その処理はStartメソッドの中で行われていますが、このような場所をコンポジション・ルートと呼びます。コンポジション・ルートは通常、アプリケーションのエントリーポイントに置かれ、Unityなら例えばStartメソッドがその場所に該当します。コンポジション・ルートでは、システムを構成するすべてのクラスのインスタンス化を行い、主に各コンストラクターに必要なオブジェクトを渡すことで、各クラス間に存在する依存関係を解決します。DIを適用した疎結合なコードでは、実装の置き換えを行いたいときに変更するのはコンポジション・ルートのみです。その他クラスに影響が及ぶことはありません。デザインパターンによるコードの再利用DIを適用した疎結合なコードでは、システムが良く設計されていれば、新たな機能を追加したいときにすでに作成されているコードを再利用することができます。そのコードに手を加える必要はありません。あるイベントで、普段のログインボーナスに追加のボーナスを付与したい場合を考えてみましょう。クラスのように、新しく作成したクラスに、すでに作成したクラスの論理を含ませる必要がありました。疎結合なコードでは、新たな論理を追加したいとき、その論理を実装するクラスは、それが付加される対象の論理がどのようなものであるかを気にする必要はありません。}}C#各日付に対する実行結果も示します。上のコードは、通常のボーナスに加えて、コイン10枚を配布します。このコードの実行結果と、初めのコードの実行結果を比較してみましょう。ポイントは、次の二つです。LoginBonusProviderExCoinDecoratorクラスはILoginBonusProviderインターフェースの実装を使用しながら、このクラス自身もまたILoginBonusProviderインターフェースを実装していることLoginServiceクラスや、DateBasedLoginBonusProviderクラスには一切の変更がなされていないことまず、LoginBonusProviderExCoinDecoratorクラスについて、インターフェースのメンバーメソッドであるGetLoginBonusの中では、内部に保持しているインターフェースの実装からログインボーナスを取得し、それにコインを足したうえでその値を返しています。これで、「通常のボーナスに加えて、追加のコインを配布する」論理が実現できます。通常のボーナスとは何なのかについては、このクラスは一切知りません。次に、通常のボーナスを付与する論理を持っていたDateBasedLoginBonusProviderクラスにも、それを使用していたLoginServiceクラスにも、一切の変更がなされていません。コンポジション・ルートが書き換わったのみです。このように、疎結合なコードでは新たな機能を追加したい際、その機能を持つクラスを他のクラスとは無関係に実装し、もともとあった機能を再利用することができます。ちなみに、DIによって再利用性を高めるためのデザインパターンがいくつかあり、今回はそのうちの一つであるデコレーター・パターンを使用しています。デコレーター・パターン今回の例では、デコレーター・パターンというデザインパターンを使用しました。デコレーター・パターンでは、デコレーターと呼ばれるクラスが、あるインターフェースに依存しつつ、自身もそのインターフェースを実装します。デコレーターは、一つのインターフェースの実装に対して追加の処理を付け加えるために使用されます。今回の例では、デコレーションされている側の返り値を加工して返すデコレーターを作成しました。ここで、もう一つの例を見ておきましょう。次のデコレーターはボーナスの値をチェックし、あまりにも多いボーナスが与えられていた時に、何かしらのエラーが発生しているとみなして例外を送出します。このデコレーターの、「異常な値を検出して例外を発生させる」という処理も、デコレーションされる側の実装とは独立しています。}}C#デコレーターはデコレーションされる側とは無関係!LoginBonusProviderExCoinDecoratorクラスは、DateBasedLoginBonusProviderクラスではなく、ILoginBonusProviderインターフェースに依存しています。これは、LoginBonusProviderExCoinDecoratorの論理が、「通常のボーナスに対してコイン10枚を追加する」というものであるよりは、より正確には「渡されたインターフェースの実装が配布するボーナスに対してコイン10枚を追加する」ものであることを意味します。ILoginBonusProviderインターフェースを実装するクラスであればなんでも渡すことができるので、例えばEventLoginBonusProviderクラスを渡して、「日付によらずボーナスがもらえるかつ、さらにそこに追加のコインがもらえる」ような論理を実現することもできます。この場合も、コンポジション・ルートを書き換えるのみで実現できます。}}C#また、テスト用のオブジェクトを用意することができるので、単体テストも簡単に行うことができます。再コンパイルすることなく、実行時にシステムの構成を変えることができる性質です。疎結合なコードでは、依存関係の解決を、コンポジション・ルートの一か所で行います。そのため、設定ファイルを読み込んで、実行時に依存関係を必要に応じて書き換えるといったことが可能です。以下のようにして、設定ファイルから読み込んだ値をもとにして、実行時にシステムの構成を変えることができます。isEventGoingの値はJSONやXMLなどのテキストファイルから読み取った値であると仮定してください。この場合、イベント開催の有無に合わせて設定ファイルの値を書き換えることで、再コンパイルなしでシステムの構成を書き換えることができます。new(loginBonusProvider);loginService.Login();C#なお、実際に遅延結合性を利用する際にはリフレクションを利用することで、より柔軟に構成を切り替えることができます。新たな機能の追加や、既存の機能の拡張におかかるコストが少ないという性質です。学んだ通り、うまく設計された疎結合なコードでは、システムの拡張の際に、既存のコードに手を加える必要がありません。新たなクラスを用意し、コンポジション・ルートを書き換えるのみで済みます。「同一のインターフェースに対する実装は、互いに可換でなければならない」という主張です。学んだように、疎結合なコードでは、クラスが依存するのはインターフェースであるため、同一のインターフェースを実装するクラスであれば、何でもそのクラスに渡すことができます。LSPを踏まえると、この「互いに置き換えることができる」という性質はDIの観点からは非常に重要であり、インターフェースに依存しているクラスは特定の実装を意識してはいけないということも言えます。「クラスは拡張に対して開いていて、変更に対して閉じているべき」という主張です。よる並行開発を効率的にできる性質です。学んだ通り、疎結合なコードでは、実際に使用される下位モジュールの開発が完了していなくとも、テスト用のオブジェクトを使用することで上位モジュールのテストを行うことができます。保守性コードのメンテナンスにかかるコストが少ないという性質です。疎結合なコードでは、各クラスが明確で単純な役割を持つようにシステムを設計することを目指します。その結果、機能拡張の際にどこを変更すればいいのかがわかりやすくなったり、不具合の発生時、トラブルシューティングが容易になったりします。「クラスが変更される理由は一つでなくてはならない」という主張です。これは言い換えれば、各クラスは単一の役割を持つべきということです。することで、ここで論じている保守性の向上が期待できます。DIにおいては、コンストラクター・インジェクションを基本的に用いますが、ここでインジェクトされる依存の数が、SRPに違反しているかどうかの良い指標になります。今回の例では高々1つの依存しかインジェクトしていませんが、インジェクトされる依存の数が3つ以下に収まっているかどうかが、SRPに違反していないかどうかの目安になるでしょう。単体テストが可能であるという性質です。学んだ通り、疎結合なコードではテスト用のクラス、テストダブルを作成することで、テスト対象の依存先が不確定性を持っていたとしても、まだ開発が完了していなくても、単体テストを行うことができます。密結合なコードでは、LoginServiceクラスがDateBasedLoginBonusProviderクラスを使用している、つまり依存しているという関係です。言い換えれば、上位のクラスが下位のクラスに直接依存しています。密結合なコードと疎結合なコードの違いを、クラスの依存関係から見てみましょう。以下に、密結合なコードと疎結合なコード、それぞれの依存関係の一部を示しています。注目すべきは、LoginServiceクラスとDateBasedLoginBonusProviderクラスの依存関係です。密結合なコードと、疎結合なコードでは何が変化したでしょうか。疎結合なコードでは、LoginServiceクラスがILoginBonusProviderインターフェースを使用し、DateBasedLoginBonusProviderクラスがこのインターフェースを実現しています。上位のクラスも、下位のクラスも、インターフェースに依存している状態です。結局のところ、密結合なコードと疎結合なコードの一番の違いは、クラスが具体的なクラスに依存押しているか、抽象的なインターフェースに依存しているかです。DIでは、各クラスが直接依存関係を持つのではなく、インターフェースを介して間接的につながることにより、コードの拡張性や、再利用性が高まります。DIPは、「上位のモジュールは下位のモジュールに依存すべきではなく、代わりに両者は抽象に依存すべきである」ということを主張しています。今回の例でいえば、LoginServiceクラスが上位のモジュール、DateBasedLoginBonusProviderクラスが下位のモジュールを意味し、インターフェース、つまりILoginBonusProviderが抽象を意味します。密結合なコードでは、上位のモジュールが下位のモジュールに依存する状態になっており、下位のモジュールの変更が上位のモジュールに影響を受ける状態でした。一方で、疎結合なコードでは上位のモジュールと下位のモジュールはインターフェースに依存しているため、各モジュールは独立して存在することができます。言えば、DIPでは「抽象に対する制御は、それを使用する側が持つべき」とされています。理想的には上位のモジュールがインターフェースに対する制御を持つべきとされます。実際に今回の例では、ILoginBonusProviderインターフェースの求められる振る舞いを規定しているのはLoginService側です。その要求された振る舞いを満たすように、DateBasedLoginBonusProviderクラスはインターフェースを実装します。これは、クラス同士が直接結びついている状態と比較して、どちらかといえば上位のモジュールに、下位のモジュールが合わせている状態です。この意味で、DIを適用した疎結合なコードでは、下位のモジュールが上位のモジュールに依存しているといえ、依存性が逆転しているといえます。(DIPでは、理想的にはインターフェースは上位のモジュールに属するべきとされますが、実際には下位のモジュールにインターフェースに属せざるをえない場合もあります。重要なのは、各クラスが直接他クラスに依存するのではなく、インターフェースに依存するということです。)インターフェースか、クラスか既に、DIの主要なアイデアが、クラス同士をインターフェースで結びつけることであることを学びました。すべてのクラスは、あらゆる場面でインターフェースに依存する必要があるのでしょうか。言い換えれば、クラスに直接依存することは、DIではありえないのでしょうか。場面によってもちろん、クラスに直接依存することもあります。なければインターフェースによる依存の抽象化が永遠に終わらないでしょう。あるクラスに対する依存をインターフェースで抽象化するべきかどうかは、場面に応じて見極める必要があります。見極めに関して、一つの指針を以下に示します。以下のいずれかに当てはまれば、クラスへの依存はインターフェースに対する依存に置き換えられるべきです。依存先に求められる要求が、将来的に変化する可能性が高い依存先に対する要求が、実行環境によって変化する依存先に不確定性がある。現在時刻や乱数の取得などクラスに求められる要求が変化する可能性が高かったり、実行環境によって異なる要求があったりする場合は、新たにインターフェースを規定することに対するコストよりも、抽象化により要求の変更に対応するためのコストが低くなることへの利点のほうが上回ります。不確定性については、抽象化によるテスト容易性がはるかに大きくなります。実際に、今回作成したシステムでは、配布されるログインボーナスの決定方法は将来的に変わる可能性が高く、日付に基づいてログインボーナスを決定する場合には、現在時刻の取得という不確定性が関わっていました。そのため、それぞれ、ILoginBonusProviderインターフェース、IDateTimeProviderインターフェースという形で依存を抽象化し、それによってシステムが変更への耐性と、テスト容易性を獲得しました。ServerなどのDBMSの差を吸収するための抽象化が挙げられます。レコードなどは、将来的に要求される振る舞いが変わることはありません。DateTime構造体もLoginBonusレコードも、単にクラス間のデータのやり取りに使われているだけであり、役割として、変更できる振る舞いをそもそも持っていないからです。第一に、不確定性を持つ依存なのか、第二に、将来変更されうる、もしくは実行環境によって変更しなければいけない振る舞いを持つ依存なのかを確認しましょう。まとめ最後に、これまでに出てきたコードや図を再掲しつつ、DIについて学んだことをまとめます。DIの目的と利点DIの目的は、「クラスに対してではなく、インターフェースに対してプログラミングする」ことにより疎結合なコードを設計し、コードの拡張性や再利用性を高めることです。独立したクラスをクラス単位でテストできるDIを適用する方法DIを適用して疎結合なコードを設計する指針は、「インターフェースでクラスをつなげること」です。あるクラスが別のクラスを直接使用するのではなく、インターフェースを使用するようにし、そのインターフェースを他のクラスに実装させるようにします。依存関係は、たいていの場合はコンストラクター・インジェクションによって実行時に解決します。}}C#依存関係を解決する場所は、コンポジション・ルートと呼ばれます。コンポジション・ルートは、普通はアプリケーションのエントリーポイントに置かれ、UnityではStart()メソッドが一つの候補になります。依存先に求められる要求が、将来的に変化する可能性が高い依存先に対する要求が、実行環境によって変化する依存先に不確定性がある。現在時刻や乱数の取得など上記のいずれかに当てはまる場合は、依存をインターフェースで抽象化したほうが良いと考えられます。すべての依存を抽象化する必要はなく、特にLoginBonusレコードのような、クラス同士がデータをやり取りするためだけに使用される、振る舞いを持たないクラスは抽象化する必要はありません。0;}C#不確定性を持つ、もしくは将来的に変更の可能性があったり、実行環境で変更する必要があったりする依存は、インターフェースで抽象化するのが望ましいでしょう。作成したLoginServiceクラスを改良し、受け取ったログインボーナスの表示方法を抽象化します。LoginBonusオブジェクトを受け取って、その内容を表示することを要求とするインターフェースを作成し、TextMeshProUGUIコンポーネントに内容を表示する実装を一つ作成しましょう。スタブを用意すると、任意の日時でテストを行うことができます。受け取ったログインボーナスの内容を、そのままデバッグコンソールに出力するデコレーターを作成しましょう。ログインボーナスの内容が、TMProには加工されてから表示され、コンソールにはそのまま表示されます。伊都アキラ関連記事UniTask【UniTask】UniTaskのキャンセルUniTaskの非同期処理は、実行途中でキャンセルすることができます。この記事では、UniTaskのキャンセル方法と、その扱いについて学習します。ScenarioFlow【ScenarioFlow】ライブラリ紹介:ScenarioFlowで会話シーンをつくろう会話シーン作成のためのUnityライブラリ、ScenarioFlowの紹介です。今回の記事では、会話シーン作成ライブラリ「ScenarioFlow」についての基本を取り扱います。S...UniTask【UniTask】UniTask非同期処理入門Unityにおいて効率的に非同期処理を行うためのライブラリであるUniTaskの入門解説です。コメントをキャンセルメールアドレスが公開されることはありません。※サイト次回のコメントで使用するためブラウザーに自分の名前、メールアドレス、サイトを保存する。認証期間が終了しました。ページを再読み込みしてください。Dotprologue.メニューホームダウンロード運営者情報お問い合わせEnglishホーム検索トップサイドバ。