Bu iş parçacığının bu noktada oldukça eski olduğunu biliyorum, ama bu konudaki düşüncelerimle karşılaşacağımı düşündüm. TL; DR, JavaScript'in türlenmemiş, dinamik doğası nedeniyle, bağımlılık enjeksiyon (DI) desenine başvurmadan veya bir DI çerçevesi kullanmadan gerçekten çok şey yapabileceğinizdir. Ancak, bir uygulama büyüdükçe ve karmaşıklaştıkça, DI kesinlikle kodunuzun korunmasına yardımcı olabilir.
C # 'de DI
DI'nin neden JavaScript'te bir ihtiyaç kadar büyük olmadığını anlamak için, C # gibi güçlü yazılan bir dile bakmak faydalıdır. (C # bilmeyenler özür dileriz, ama takip etmek yeterince kolay olmalı.) Bir araba ve boynuzu açıklayan bir uygulamamız olduğunu varsayalım. İki sınıf tanımlarsınız:
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();
}
}
Kodu bu şekilde yazmanın birkaç sorunu vardır.
Car
Sınıf sıkıca boynuzun özel uygulama ile bağlanmıştır Horn
sınıfı. Arabanın kullandığı korna türünü değiştirmek istiyorsak, korna Car
kullanımı değişmese bile sınıfı değiştirmek zorundayız . Bu ayrıca testi zorlaştırır çünkü Car
sınıfı bağımlılığından, Horn
sınıftan ayrı olarak test edemeyiz .
Car
Sınıf yaşam döngüsü sorumludur Horn
sınıfın. Bunun gibi basit bir örnekte bu büyük bir sorun değildir, ancak gerçek uygulamalarda bağımlılıkların bağımlılıkları, vb. Car
Olacak bağımlılıkları olacaktır. Bu sadece karmaşık ve tekrarlayıcı olmakla kalmaz, aynı zamanda sınıfın “tek sorumluluğunu” da ihlal eder. Örnek oluşturmaya değil, araba olmaya odaklanmalıdır.
- Aynı bağımlılık örneklerini yeniden kullanmanın bir yolu yoktur. Yine, bu oyuncak uygulamasında önemli değildir, ancak bir veritabanı bağlantısını düşünün. Genellikle uygulamanızda paylaşılan tek bir örneğiniz olur.
Şimdi bir bağımlılık enjeksiyon modeli kullanmak için bunu yeniden düzenleyelim.
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();
}
}
Burada iki önemli şey yaptık. İlk olarak, Horn
sınıfımızın uyguladığı bir arayüz geliştirdik. Bu, Car
sınıfı belirli bir uygulama yerine arabirime kodlamamızı sağlar. Şimdi kod uygulayan her şeyi alabilir IHorn
. İkincisi, korna örneklemesini çıkardık Car
ve yerine geçtik . Bu, yukarıdaki sorunları çözer ve belirli örnekleri ve yaşam döngülerini yönetmek için uygulamanın ana işlevine bırakır.
Bunun anlamı, araca Car
sınıfa dokunmadan kullanmak için yeni bir boynuz türü getirebilmesidir :
class FrenchHorn : IHorn
{
public void Honk()
{
Console.WriteLine("le beep!");
}
}
Main FrenchHorn
bunun yerine sınıfın bir örneğini enjekte edebilir . Bu aynı zamanda testi önemli ölçüde basitleştirir. Yalnızca MockHorn
sınıfı Car
tek başına test ettiğinizden emin olmak için yapıcıya enjekte edilecek bir sınıf oluşturabilirsiniz Car
.
Yukarıdaki örnek, manuel bağımlılık enjeksiyonunu göstermektedir. Tipik olarak DI bir çerçeve ile yapılır (örneğin C # dünyasında Unity veya Ninject ). Bu çerçeveler, bağımlılık grafiğinizi yürüterek ve gerektiği gibi örnekler oluşturarak sizin için tüm bağımlılık kablolarını yapacaktır.
Standart Node.js Yolu
Şimdi Node.js'deki aynı örneğe bakalım. Muhtemelen kodumuzu 3 modüle böleriz:
// 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 türetilmediğinden, daha önce sahip olduğumuzla aynı sıkı bağlantıya sahip değiliz. car
Modül, sadece modülün dışa honk
aktardığı yöntemle ilgili yöntemi çağırmaya çalışacağından, arabirimlere gerek yoktur (bunlar da yoktur) horn
.
Ayrıca, Düğümün require
her şeyi önbelleğe aldığı için, modüller aslında bir kapta saklanan tekil düğmelerdir. Bir gerçekleştiren Başka modül require
üzerinde horn
modül aynı örneği alacak. Bu, veritabanı bağlantıları gibi tek nesnelerin paylaşılmasını çok kolaylaştırır.
Şimdi hala car
modülün kendi bağımlılığını getirmekten sorumlu olduğu sorunu var horn
. Arabanın kornası için farklı bir modül kullanmasını istiyorsanız require
, car
modüldeki ifadeyi değiştirmeniz gerekir . Bu çok yaygın bir şey değildir, ancak test ile ilgili sorunlara neden olur.
İnsanların test problemini ele alma alışkanlığı proxyquire'dir . JavaScript'in dinamik doğası nedeniyle proxyquire, çağrıları gerektirecek çağrıları keser ve bunun yerine sağladığınız saplamaları / alayları döndürür.
var proxyquire = require('proxyquire');
var hornStub = {
honk: function () {
console.log("test beep!");
}
};
var car = proxyquire('./car', { './horn': hornStub });
// Now make test assertions on car...
Bu, çoğu uygulama için fazlasıyla yeterlidir. Uygulamanız için çalışıyorsa, onunla devam edin. Ancak, deneyimlerime göre, uygulamalar büyüdükçe ve daha karmaşık hale geldikçe, bunun gibi kodların korunması zorlaşmaktadır.
JavaScript'te DI
Node.js çok esnektir. Yukarıdaki yöntemden memnun değilseniz, bağımlılık enjeksiyon desenini kullanarak modüllerinizi yazabilirsiniz. Bu modelde her modül bir fabrika işlevini (veya bir sınıf yapıcısını) dışa aktarır.
// 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();
Bu, index.js
modülün örneğin yaşam döngüleri ve kablolamadan sorumlu olması nedeniyle C # yöntemine çok benzer . Ünite testleri oldukça basittir, çünkü işlevlere alayları / saplamaları geçirebilirsiniz. Yine, bu uygulama için yeterince iyi ise onunla gitmek.
Bolus DI Çerçevesi
C # 'dan farklı olarak, bağımlılık yönetiminize yardımcı olacak yerleşik bir standart DI çerçevesi yoktur. Npm kayıt defterinde birkaç çerçeve vardır, ancak hiçbirinin yaygın olarak benimsenmesi yoktur. Bu seçeneklerin birçoğu diğer yanıtlarda belirtilmiştir.
Mevcut seçeneklerden herhangi biriyle özellikle mutlu değildim, bu yüzden kendi bolus'umu yazdım . Bolus, yukarıdaki DI tarzında yazılmış kodla çalışacak şekilde tasarlanmıştır ve çok KURU ve çok basit olmaya çalışır . Yukarıdaki modül car.js
ve horn.js
modüllerin aynısını kullanarak, index.js
modülü bolus ile aşağıdaki gibi yeniden yazabilirsiniz :
// index.js
var Injector = require("bolus");
var injector = new Injector();
injector.registerPath("**/*.js");
var car = injector.resolve("car");
car.honkHorn();
Temel fikir, bir enjektör oluşturmanızdır. Tüm modüllerinizi enjektöre kaydedersiniz. Sonra ihtiyacınız olanı çözersiniz. Bolus bağımlılık grafiğinde yürüyecek ve gerektiğinde bağımlılıklar yaratacak ve enjekte edecektir. Böyle bir oyuncak örneğinde fazla tasarruf yapmazsınız, ancak karmaşık bağımlı ağaçlara sahip büyük uygulamalarda tasarruflar çok büyüktür.
Bolus, isteğe bağlı bağımlılıklar ve test globalleri gibi bir dizi şık özelliği destekler, ancak standart Node.js yaklaşımına göre gördüğüm iki önemli avantaj vardır. İlk olarak, çok sayıda benzer uygulamanız varsa, tabanınız için bir enjektör oluşturan ve üzerine yararlı nesneler kaydeden özel bir npm modülü oluşturabilirsiniz. Ardından, belirli uygulamalarınız AngularJS'lerin yaptığı gibi gerektiği gibi ekleyebilir, geçersiz kılabilir ve çözülebilirenjektör çalışır. İkinci olarak, çeşitli bağımlılık bağlamlarını yönetmek için bolus kullanabilirsiniz. Örneğin, isteğe bağlı olarak bir alt enjektör oluşturmak, kullanıcı kimliğini, oturum kimliğini, kaydediciyi vb. Bunlara bağlı olarak herhangi bir modülle birlikte enjektöre kaydetmek için ara katman yazılımı kullanabilirsiniz. Ardından, istekleri sunmak için gerekeni çözün. Bu, istek başına modüllerinizin örneklerini verir ve her modül işlev çağrısı boyunca kaydediciyi vb. Geçirmeyi engeller.