JavaScript’te Bağımlılık Enjeksiyon Konteyneri – JSManifest

JavaScript'te Bağımlılık Enjeksiyon Konteyneri – JSManifest

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 Toadsonra Frogsonra geri Toadvb.

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, frogOwnerLicensevefrog.

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.

javascript'te bağımlılık-enjeksiyon-konteyner-bağımlılıkları

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.

javascript'te bağımlılık-enjeksiyon-konteyner-get-yöntemi

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.

JavaScript-injection-container-inject-in-javascript bağımlılığı)

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
  }
}

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.