このスレッドは今のところかなり古いことは知っていますが、私はこれについて自分の考えを理解していると思います。TL; DRは、JavaScriptの型付けされていない動的な性質により、依存性注入(DI)パターンに頼ったり、DIフレームワークを使用したりせずに、実際に多くのことができるということです。ただし、アプリケーションが大きく複雑になるにつれて、DIはコードの保守性を確実に支援します。
C#のDI
JavaScriptでDIがそれほど必要ではない理由を理解するには、C#のような強く型付けされた言語を調べると役立ちます。(C#を知らない人にはお詫びしますが、簡単に理解できるはずです。)車とそのホーンを説明するアプリがあるとします。次の2つのクラスを定義します。
class Horn
{
public void Honk()
{
Console.WriteLine("beep!");
}
}
class Car
{
private Horn horn;
public Car()
{
this.horn = new Horn();
}
public void HonkHorn()
{
this.horn.Honk();
}
}
class Program
{
static void Main()
{
var car = new Car();
car.HonkHorn();
}
}
この方法でコードを記述する場合、いくつかの問題があります。
Car
クラスが密にホーンの特定の実装に結合されているHorn
クラス。車が使用するホーンのタイプを変更したい場合は、ホーンの使用Car
方法が変更されていなくても、クラスを変更する必要があります。またCar
、依存関係であるHorn
クラスから独立してクラスをテストすることができないため、テストが困難になります。
Car
クラスはのライフサイクルに責任があるHorn
クラス。このような単純な例では、それは大きな問題ではありませんが、実際のアプリケーションでは、依存関係に依存関係があり、依存関係などCar
があります。クラスは、依存関係のツリー全体を作成する必要があります。これは複雑で反復的なだけでなく、クラスの「単一の責任」に違反します。インスタンスを作成するのではなく、自動車であることを重視する必要があります。
- 同じ依存関係インスタンスを再利用する方法はありません。繰り返しになりますが、これはこのおもちゃのアプリケーションでは重要ではありませんが、データベース接続を検討してください。通常、アプリケーション全体で共有される単一のインスタンスがあります。
次に、依存関係注入パターンを使用するようにこれをリファクタリングしましょう。
interface IHorn
{
void Honk();
}
class Horn : IHorn
{
public void Honk()
{
Console.WriteLine("beep!");
}
}
class Car
{
private IHorn horn;
public Car(IHorn horn)
{
this.horn = horn;
}
public void HonkHorn()
{
this.horn.Honk();
}
}
class Program
{
static void Main()
{
var horn = new Horn();
var car = new Car(horn);
car.HonkHorn();
}
}
ここでは2つの重要なことを行いました。最初に、Horn
クラスが実装するインターフェースを導入しました。これによりCar
、特定の実装ではなく、インターフェイスにクラスをコーディングできます。これで、コードはを実装するものなら何でも取ることができますIHorn
。次に、ホーンのインスタンス化を外しCar
て、代わりに渡しました。これにより、上記の問題が解決され、特定のインスタンスとそのライフサイクルを管理するアプリケーションのメイン機能に任されます。
これが意味することは、Car
クラスに触れずに車が使用できる新しいタイプのホーンを導入できるということです。
class FrenchHorn : IHorn
{
public void Honk()
{
Console.WriteLine("le beep!");
}
}
メインはFrenchHorn
代わりにクラスのインスタンスを注入するだけで済みます。これにより、テストも大幅に簡素化されます。コンストラクターMockHorn
に注入するクラスを作成して、クラスのみを個別にCar
テストしていることを確認できますCar
。
上記の例は、手動による依存性注入を示しています。通常、DIはフレームワーク(C#の世界ではUnityやNinjectなど)で行われます。これらのフレームワークは、依存関係グラフをたどり、必要に応じてインスタンスを作成することにより、すべての依存関係の配線を行います。
標準のNode.jsの方法
次に、Node.jsの同じ例を見てみましょう。おそらく、コードを3つのモジュールに分割します。
// horn.js
module.exports = {
honk: function () {
console.log("beep!");
}
};
// car.js
var horn = require("./horn");
module.exports = {
honkHorn: function () {
horn.honk();
}
};
// index.js
var car = require("./car");
car.honkHorn();
JavaScriptは型付けされていないため、以前とまったく同じ密結合はありません。car
モジュールはモジュールがエクスポートhonk
するものに対してメソッドを呼び出そうとするだけなので、インターフェースは必要ありません(存在しない)horn
。
さらに、Node require
はすべてをキャッシュするため、モジュールは基本的にコンテナに格納されたシングルトンです。モジュールで実行する他require
のhorn
モジュールは、まったく同じインスタンスを取得します。これにより、データベース接続などのシングルトンオブジェクトの共有が非常に簡単になります。
今でも、car
モジュールが独自の依存関係を取得する責任があるという問題がありますhorn
。車のホーンに別のモジュールを使用する場合require
は、car
モジュールのステートメントを変更する必要があります。これはあまり一般的ではありませんが、テストで問題が発生します。
テスト問題を処理する通常の方法は、proxyquireを使用することです。JavaScriptの動的な性質により、proxyquireはrequireの呼び出しをインターセプトし、代わりに提供するスタブ/モックを返します。
var proxyquire = require('proxyquire');
var hornStub = {
honk: function () {
console.log("test beep!");
}
};
var car = proxyquire('./car', { './horn': hornStub });
// Now make test assertions on car...
これは、ほとんどのアプリケーションで十分です。アプリで機能する場合は、それを使用してください。ただし、私の経験では、アプリケーションがより大きく、より複雑になるにつれて、このようなコードを維持することは困難になります。
JavaScriptのDI
Node.jsは非常に柔軟です。上記の方法に満足できない場合は、依存性注入パターンを使用してモジュールを作成できます。このパターンでは、すべてのモジュールがファクトリー関数(またはクラスコンストラクター)をエクスポートします。
// horn.js
module.exports = function () {
return {
honk: function () {
console.log("beep!");
}
};
};
// car.js
module.exports = function (horn) {
return {
honkHorn: function () {
horn.honk();
}
};
};
// index.js
var horn = require("./horn")();
var car = require("./car")(horn);
car.honkHorn();
これは、index.js
モジュールがインスタンスのライフサイクルと配線を担当するという点で、以前のC#メソッドとよく似ています。ユニットテストは、モック/スタブを関数に渡すだけなので非常に簡単です。繰り返しますが、これがアプリケーションにとって十分なものであれば、それで問題ありません。
ボーラスDIフレームワーク
C#とは異なり、依存関係の管理に役立つ標準のDIフレームワークは確立されていません。npmレジストリにはいくつかのフレームワークがありますが、広く採用されているものはありません。これらのオプションの多くは、他の回答ですでに引用されています。
利用できるオプションに特に満足していなかったので、独自のbolusを作成しました。Bolusは、上記のDIスタイルで記述されたコードで動作するように設計されており、非常にDRYで非常にシンプルにしようとしています。上記car.js
とまったく同じhorn.js
モジュールを使用して、index.js
ボーラスでモジュールを次のように書き換えることができます。
// index.js
var Injector = require("bolus");
var injector = new Injector();
injector.registerPath("**/*.js");
var car = injector.resolve("car");
car.honkHorn();
基本的な考え方は、インジェクターを作成することです。すべてのモジュールをインジェクターに登録します。次に、単に必要なものを解決します。ボーラスは依存関係グラフをたどり、必要に応じて依存関係を作成して挿入します。このようなおもちゃの例ではあまり節約できませんが、複雑な依存関係ツリーを持つ大規模なアプリケーションでは、節約量は膨大です。
Bolusは、オプションの依存関係やテストグローバルなどの多くの気の利いた機能をサポートしていますが、標準のNode.jsアプローチと比較して、私が見た2つの主要な利点があります。最初に、類似したアプリケーションがたくさんある場合は、ベース用のプライベートnpmモジュールを作成して、インジェクターを作成し、それに便利なオブジェクトを登録できます。次に、特定のアプリが、AngularJSの方法と同様に、必要に応じて追加、オーバーライド、および解決できます。インジェクターは動作します。次に、ボーラスを使用して、依存関係のさまざまなコンテキストを管理できます。たとえば、ミドルウェアを使用して、リクエストごとに子インジェクターを作成し、インジェクターにユーザーID、セッションID、ロガーなどを、それらに依存するモジュールとともに登録できます。次に、リクエストを処理するために必要なものを解決します。これにより、リクエストごとにモジュールのインスタンスが提供され、すべてのモジュール関数呼び出しにロガーなどを渡す必要がなくなります。