
JavaScript, doğası gereği esnek olması nedeniyle birçok tekniğe sahiptir. Bu yazıda, Dependency Injection Container’ı inceleyeceğiz.
Bu model aslında Dependency Injection ile aynı amacı sağlar, ancak daha esnek ve güçlü bir şekilde, ihtiyaç duyduklarında, örneğin başlatma aşamasında olduğu gibi, bunları gerektiren işlevlerin (veya sınıfların) bağımlılıklarını barındıran kapsayıcı olarak hareket ederek.
Kapsayıcı Olmadan Bağımlılık Enjeksiyonu
Dependency Injection’ın ne olduğu, kodda nasıl göründüğü, hangi sorunları çözdüğü ve hangi sorunlardan muzdarip olduğu konusunda hızlıca fikirlerimizi tazeleyelim.
Bağımlılık Enjeksiyonu, modüllerdeki sabit kodlama bağımlılıklarından kaçınmaya yardımcı olan, arayan kişiye bunları değiştirme ve isterse tek bir yerde kendi ihtiyaçlarını sağlama gücü veren bir kalıptır.
Bu bağımlılıklar olabilir enjekte içine kurucu (örnekleme) aşaması veya daha sonra bazıları tarafından ayarlanabilir ayarlayıcı yöntemi:
class Frog {
constructor(name, gender) {
this.name = name
this.gender = gender
}
jump() {
console.log('jumped')
}
}
class Toad {
constructor(habitat, name, gender) {
this.habitat = habitat
this.frog = new Frog(name, gender)
}
}
const mikeTheToad = new Toad('land', 'mike', 'male')
Bununla ilgili bazı sorunlar var:
Sorun 1: Nasıl değiştirmemiz gerekirse Toad
inşa edildi ve argümanların konumlandırılması veya bunların veri yapısı gibi kırılgan bir şey gerektiriyordu, çünkü kodu manuel olarak değiştirmemiz gerekecekti. kodlanmış onların kod bloğuna.
Bu senaryoya bir örnek, Frog
sınıf.
biri için, eğer Frog
katma a üçüncü parametre gibi yapıcısında weight
:
class Frog {
constructor(name, gender, weight) {
this.name = name
this.gender = gender
this.weight = weight
}
jump() {
console.log('jumped')
}
}
O zaman bizim Toad
zorunlu güncellenecek çünkü bu yeni bağımlılık aramıza eklendi Frog
somutlaştırma:
class Toad {
constructor(habitat, name, gender, weight) {
this.habitat = habitat
this.frog = new Frog(name, gender, weight)
}
}
Bu şekilde devam edersek, kaç kez değişmek zorunda kalacağını düşünüyorsun? Toad
Bir kurbağa başlangıç şirketinde olsaydınız ve bu, başladığınız ilk kod parçalarından biri olsaydı?
2. Sayı: Hangi bağımlılığı kullanacağınızı bilmelisiniz Toad
her zaman.
Biz Sahip olmak bunu bilmek Toad
şimdi 4 argümana ihtiyacı var aynı sıra örneğini başlatmak için Frog
doğru, hatta onların veri tipleri aksi takdirde hatalar kolayca oluşabilir.
Ve eğer biliyorsan, oldukça garip görünebilir. Toad
dır-dir aslında bir kurbağa, bu yüzden bunu bilerek, yanlışlıkla Toad
uzayacak Frog
sonra. O zaman anlarsın ki bir örneğinin Frog
içeride yaratılıyor Toad
bunun yerine ve şimdi kafanız karışıyor çünkü siz zeki bir insansınız ve kod sizi sadece yoldan atıyordu – kodun gerçek dünyayla düzgün bir şekilde hizalanmadığını fark ederek.
Sayı #3: Gereksiz yere daha fazla kod içeriyor
Dependency Injection modeli ile bu problemler şu şekilde çözülür: kontrolü tersine çevirmek bağımlılıkların somutlaştırılma şekli:
class Frog {
constructor({ name, gender, weight }) {
this.name = name
this.gender = gender
this.weight = weight
}
jump() {
console.log('jumped')
}
}
class Toad {
constructor(habitat, frog) {
this.habitat = habitat
this.frog = frog
}
}
Tamam, bu kolayca oldu. Şimdi başka bir kırılma değişikliği olduğunda Frog
(bir JavaScript’e konulan argümanlar gibi nesne), dokunmamıza bile gerek yok Toad
ya da beyin hücrelerinin okumasını boşa harcamak Toad
sonra Frog
sonra geri Toad
vb.
Bunun nedeni, artık örneğini oluşturduğumuz kısmı değiştirebilmemizdir. Toad
(ki bu, içeri girip içindekileri değiştirmek zorunda kalmaktan daha iyidir) Toad
uygulama – ki bu kötü uygulama! BT yapmamalı kurbağanın nasıl inşa edildiği konusunda endişelenmeli – yalnızca bir kurbağayı argüman olarak aldığını ve onu kendi içinde sakladığını bilmelidir. .frog
daha sonra kullanılacak özellik. Sen şimdi bağımlılıklarında sorumluluk al.
const mikeTheToad = new Toad(
'land',
new Frog({
name: 'mike',
gender: 'male',
weight: 12.5,
}),
)
Bu nedenle, uygulama ayrıntılarını soyutlayarak bazı temiz kod uygulamaları uyguladık. Frog
uzak Toad
yapıcı. mantıklı: yapar Toad
ilgilenmek zorunda bile nasıl Frog
inşa edilmiş mi? Bir şey varsa, sadece uzatmalıydı!
Bağımlılık Enjeksiyon Kabı (DIC) Kalıbı
Dependency Injection konusunda zihnimizi tazelediğimize göre şimdi Dependency Injection Container’dan bahsedelim!
Öyleyse neden DIC modeline ihtiyacımız var ve neden değil Bağımlılık Enjeksiyonu olmadan konteyner yeterli zor durumlarda?
İşte sorun: Bu sadece ölçeklenebilir değil. Projeniz ne kadar büyük olursa, uzun vadede kodunuzu koruma konusundaki güveninizi o kadar fazla kaybetmeye başlarsınız çünkü o zaman zaman içinde sadece bir karmaşa haline gelir. Ayrıca, şunları da almanız gerekir: bağımlılıkları doğru sırayla enjekte etme sırası böylece bir şeyin varlığı konusuna düşmezsiniz undefined
bir şeyi somutlaştırırken.
Yani özünde, 6 ay sonra kodumuz şöyle bir şeye dönüşebilir:
class Frog {
constructor({ name, gender, weight }) {
this.name = name
this.gender = gender
this.weight = weight
}
jump() {
console.log('jumped')
}
setHabitat(habitat) {
this.habitat = habitat
}
}
class Toad extends Frog {
constructor(options) {
super(options)
}
leap() {
console.log('leaped')
}
}
class Person {
constructor() {
this.id = createId()
}
setName(name) {
this.name = name
return this
}
setGender(gender) {
this.gender = gender
return this
}
setAge(age) {
this.age = age
return this
}
}
function createId() {
var idStrLen = 32
var idStr = (Math.floor(Math.random() * 25) + 10).toString(36) + '_'
idStr += new Date().getTime().toString(36) + '_'
do {
idStr += Math.floor(Math.random() * 35).toString(36)
} while (idStr.length < idStrLen)
return idStr
}
class FrogAdoptionFacility {
constructor(name, description, location) {
this.name = name
this.description = description
this.location = location
this.contracts = {}
this.adoptions = {}
}
createContract(employee, client) {
const contractId = createId()
this.contracts[contractId] = {
id: contractId,
preparer: employee,
client,
signed: false,
}
return this.contracts[contractId]
}
signContract(id, signee) {
this.contracts[id].signed = true
}
setAdoption(frogOwner, frogOwnerLicense, frog, contract) {
const adoption = {
[frogOwner.id]: {
owner: {
firstName: frogOwner.owner.name.split(' ')[0],
lastName: frogOwner.owner.name.split(' ')[1],
id: frogOwner.id,
},
frog,
contract,
license: {
id: frogOwnerLicense.id,
},
},
}
this.adoptions[contract.id] = adoption
}
getAdoption(id) {
return this.adoptions[id]
}
}
class FrogParadiseLicense {
constructor(frogOwner, licensePreparer, frog, location) {
this.id = createId()
this.client = {
firstName: frogOwner.name.split(' ')[0],
lastName: frogOwner.name.split(' ')[1],
id: frogOwner.id,
}
this.preparer = {
firstName: licensePreparer.name.split(' ')[0],
lastName: licensePreparer.name.split(' ')[1],
id: licensePreparer.id,
}
this.frog = frog
this.location = `${location.street} ${location.city} ${location.state} ${location.zip}`
}
}
class FrogParadiseOwner {
constructor(frogOwner, frogOwnerLicense, frog) {
this.id = createId()
this.owner = {
id: frogOwner.id,
firstName: frogOwner.name.split(' ')[0],
lastName: frogOwner.name.split(' ')[1],
}
this.license = frogOwnerLicense
this.frog = frog
}
createDocument() {
return JSON.stringify(this, null, 2)
}
}
Çok güzel bir uygulamamız var – müşterilerin gelip bir kurbağayı sahiplenebileceği bir kurbağa evlat edinme tesisi. Ancak evlat edinme süreci basit bir para verme/alma işlemi değildir. Kurbağaları yeni sahiplerine teslim eden her kurbağa evlat edinme tesisi için bu sürecin yürütülmesini gerektiren bir yasa varmış gibi davranıyoruz.
Tüm evlat edinme süreci ne zaman sona erer? setAdoption
itibaren FrogAdoptionFacility
denir.
Bu sınıfları kullanarak kod geliştirmeye başladığınızı ve aşağıdaki gibi çalışan bir sürümle sonuçlandığınızı varsayalım:
const facilityTitle = 'Frog Paradise'
const facilityDescription =
'Your new one-stop location for fresh frogs from the sea! ' +
'Our frogs are housed with great care from the best professionals all over the world. ' +
'Our frogs make great companionship from a wide variety of age groups, from toddlers to ' +
'senior adults! What are you waiting for? ' +
'Buy a frog today and begin an unforgettable adventure with a companion you dreamed for!'
const facilityLocation = {
address: '1104 Bodger St',
suite: '#203',
state: 'NY',
country: 'USA',
zip: 92804,
}
const frogParadise = new FrogAdoptionFacility(
facilityTitle,
facilityDescription,
facilityLocation,
)
const mikeTheToad = new Toad({
name: 'mike',
gender: 'male',
weight: 12.5,
})
const sally = new Person()
sally
.setName('sally tran')
.setGender('female')
.setAge(27)
const richardTheEmployee = new Person()
richardTheEmployee
.setName('richard rodriguez')
.setGender('male')
.setAge(77)
const contract = frogParadise.createContract(richardTheEmployee, sally)
frogParadise.signContract(contract.id, sally)
const sallysLicense = new FrogParadiseLicense(
sally,
richardTheEmployee,
mikeTheToad,
facilityLocation,
)
const sallyAsPetOwner = new FrogParadiseOwner(sally, sallysLicense, mikeTheToad)
frogParadise.setAdoption(sallyAsPetOwner, sallysLicense, mikeTheToad, contract)
const adoption = frogParadise.getAdoption(contract.id)
console.log(JSON.stringify(adoption, null, 2))
Kodu çalıştırırsak, çalışacak ve bize şuna benzeyen yeni bir benimseme nesnesi oluşturacaktır:
{
"t_k8pgj8gh_k4ofadkj2x4yluemfgvmm": {
"owner": {
"firstName": "sally",
"lastName": "tran",
"id": "t_k8pgj8gh_k4ofadkj2x4yluemfgvmm"
},
"frog": {
"name": "mike",
"gender": "male",
"weight": 12.5
},
"contract": {
"id": "m_k8pgj8gh_kdfr55oui28c88lisswak",
"preparer": {
"id": "n_k8pgj8gh_uxlbmbflwjrj4cqgjyvyw",
"name": "richard rodriguez",
"gender": "male",
"age": 77
},
"client": {
"id": "h_k8pgj8gh_hkqvp4f3uids8uj00i47d",
"name": "sally tran",
"gender": "female",
"age": 27
},
"signed": true
},
"license": {
"id": "y_k8pgj8gh_0qnwm9po0cj7p3vgsedu3"
}
}
}
Çok güzel bir uygulamamız var – müşterilerin gelip bir kurbağayı sahiplenebileceği bir kurbağa evlat edinme tesisi. Ancak evlat edinme süreci basit bir para verme/alma işlemi değildir. Kurbağaları yeni sahiplerine teslim eden her kurbağa evlat edinme tesisi için bu sürecin yürütülmesini gerektiren bir yasa varmış gibi davranıyoruz.
Yani tesisi gerektirir (Kurbağa Cenneti) müşterinin imzasını gerektiren bir sözleşme oluşturmak. Ardından, müşterinin yasal koruma için üzerinde olması gereken yerde de bir lisans oluşturulur. Ve nihayet, her şey yapıldıktan sonra evlat edinme tamamlanır.
şuna bir göz atın FrogOwner
sınıf:
class FrogParadiseOwner {
constructor(frogOwner, frogOwnerLicense, frog) {
this.id = createId()
this.owner = frogOwner
this.license = frogOwnerLicense
this.frog = frog
}
createDocument() {
return JSON.stringify(this, null, 2)
}
}
Üç bağımlılığı vardır: frogOwner
, frogOwnerLicense
vefrog
.
ile bir güncelleme olduğunu varsayalım frogOwner
(bir örneği Person
) ve bir örneği olmak için değişti Client
:
class Client extends Person {
setName(name) {
this.name = name
}
}
Şimdi başlatmaya çağırıyor FrogParadiseOwner
güncellenmesi gerekiyor.
Ama ya başlatmış olsaydık FrogParadiseOwner
kodumuzun çeşitli konumları boyunca? Kodumuz uzarsa ve bu örneklerin sayısı artarsa, bakımı o kadar sorun olur.
Burası Bağımlılık Enjeksiyon Konteyneri fark yaratabilir, çünkü kodunuzu yalnızca bir yerde değiştirmeniz gerekir.
Bir bağımlılık enjeksiyon kabı şöyle görünebilir:
import parseFunction from 'parse-function'
const app = parseFunction({
ecmaVersion: 2017,
})
class DIC {
constructor() {
this.dependencies = {}
this.factories = {}
}
register(name, dependency) {
this.dependencies[name] = dependency
}
factory(name, factory) {
this.factories[name] = factory
}
get(name) {
if (!this.dependencies[name]) {
const factory = this.factories[name]
if (factory) {
this.dependencies[name] = this.inject(factory)
} else {
throw new Error('No module found for: ' + name)
}
}
return this.dependencies[name]
}
inject(factory) {
const fnArgs = app.parse(factory).args.map((arg) => this.get(arg))
return new factory(...fnArgs)
}
}
Bu yerindeyken, değişiklikleri güncellemek bu kadar kolay hale gelir:
class Client extends Person {
setName(name) {
this.name = name
}
}
const dic = new DIC()
dic.register('frogOwner', Client)
dic.register('frogOwnerLicense', sallysLicense)
dic.register('frog', mikeTheToad)
dic.factory('frog-owner', FrogParadiseOwner)
const frogOwner = dic.get('frog-owner')
Şimdi, daha önce olduğu gibi doğrudan başlatmak ve kodun diğer tüm örneklerini değiştirmek zorunda kalmak yerine:
const frogOwner = new FrogParadiseOwner(Client, sallysLicense, mikeTheToad)
const frogOwner2 = new FrogParadiseOwner(...)
const frogOwner3 = new FrogParadiseOwner(...)
const frogOwner4 = new FrogParadiseOwner(...)
const frogOwner5 = new FrogParadiseOwner(...)
Bunun yerine güncellemek için DIC’yi kullanabilirsiniz. bir Zamanlar ve kodunuzun diğer kısımlarını değiştirmeniz gerekmeyecek, çünkü biz yönü tersine çevirdi bunun için konteynere akışın:
const dic = new DIC()
dic.register('frogOwner', Client)
dic.register('frogOwnerLicense', sallysLicense)
dic.register('frog', mikeTheToad)
dic.factory('frog-owner', FrogParadiseOwner)
const frogOwner = dic.get('frog-owner')
DIC’nin ne yaptığını açıklayalım:
DIC tarafından çözümlenmesini istediğiniz sınıfları veya işlevleri, DIC’ye geçirerek eklersiniz. .factory()
içine depolanan yöntem .factory
Emlak.
Aktarılan bu işlevlerin her biri için .factory
argümanlarını kullanarak kaydetmeniz gerekir .register()
böylece kap, istenen işlevi başlatırken alınabilirler. Onlardan alınırlar .dependencies
Emlak. kullanarak bağımlılıklara bir şeyler ekleyebilirsiniz. .dependencies()
yöntem.
Bir şeyi geri almak istediğinizde, .get
bazılarıyla key
. kullanır key
onun aracılığıyla bakmak dependencies
ve orada bir şey bulursa geri verir. Aksi takdirde, incelemeye devam edecektir. factories
ve eğer bir şey bulursa, onu çözmesini istediğiniz bir fonksiyon olarak ele alacaktır.
Sonra çağrıyı geçer .inject
içinde işlevin bağımlılıklarının (argümanlarının) adlarını okur ve bunları kendi .dependencies
özelliği, işlevi çağırmak ve enjekte argümanları, sonucu döndürür.
)
Kullandığım kod örneklerimizde parse-function
izin vermek inject
Bir fonksiyonun argümanlarını isimlerini alma yöntemi.
Kitaplık olmadan yapmak için, fazladan bir argüman ekleyebilirsiniz. .get
ve ona geçmesini sağlayın .inject
bunun gibi:
class DIC {
constructor() {
this.dependencies = {}
this.factories = {}
}
register(name, dependency) {
this.dependencies[name] = dependency
}
factory(name, factory) {
this.factories[name] = factory
}
get(name, args) {
if (!this.dependencies[name]) {
const factory = this.factories[name]
if (factory) {
this.dependencies[name] = this.inject(factory, args)
} else {
throw new Error('No module found for: ' + name)
}
}
return this.dependencies[name]
}
inject(factory, args = []) {
const fnArgs = args.map((arg) => this.get(arg))
return new factory(...fnArgs)
}
}
Yine de aynı sonucu alıyoruz:
const dic = new DIC()
dic.register('frogOwner', Client)
dic.register('frogOwnerLicense', sallysLicense)
dic.register('frog', mikeTheToad)
dic.factory('frog-owner', FrogParadiseOwner)
const frogOwner = dic.get('frog-owner', [
'frogOwner',
'frogOwnerLicense',
'frog',
])
console.log('frog-owner', JSON.stringify(frogOwner, null, 2))
Sonuç:
{
"id": "u_k8q16rjx_fgrw6b0yb528unp3trokb",
"license": {
"id": "m_k8q16rjk_jipoch164dsbpnwi23xin",
"client": {
"firstName": "sally",
"lastName": "tran",
"id": "b_k8q16rjk_0xfqodlst2wqh0pxcl91j"
},
"preparer": {
"firstName": "richard",
"lastName": "rodriguez",
"id": "g_k8q16rjk_f13fbvga6j2bjfmriir63"
},
"frog": {
"name": "mike",
"gender": "male",
"weight": 12.5
},
"location": "undefined undefined NY 92804"
},
"frog": {
"name": "mike",
"gender": "male",
"weight": 12.5
}
}