JavaScript’te Komut Tasarım Modeli – JSManifest

JavaScript'te Komut Tasarım Modeli – JSManifest

JavaScript’te, insanların kullanmayı sevdiği en popüler tasarım kalıplarından biri, Komut Tasarım Modeligeliştiricilerin bir şey isteyen nesneleri, istedikleri yöntemleri çağırmak isteyenlerden ayırmasına olanak tanıyan bir kalıp.

Komut düzenini ilk kez duyuyorsanız, umarım bu gönderiyi okuyarak bunun ne olduğunu, nasıl çalıştığını ve belirli durumlarda bunlara neden ihtiyacımız olduğunu iyi anlarsınız.

Komut tasarım modeli nedir?

Tasarım desenleri genellikle şu şekilde sınıflandırılır: üç farklı kategori türüve bu durumda komut deseni davranışsal bir.

Bunun nedeni, amacının, hangi yöntemlerin çağrılacağına ve içeride ne olacağına karar vermenin çifte sorumluluğu olan nesneleri kapsüllemektir.

Görsel bir perspektifte, bu şöyle görünebilir:

Nasıl çalışır

Bu nedenle, esas olarak görevi, iletişimi ayrı nesnelere bölerek, nihai hedefi korurken gevşek bir şekilde bağlanmalarını sağlamaktır.

Bu modele dahil olan katılımcılara genel olarak şu adlar verilir:

Müşteri

Müşterinin sorumluluğu, oluşturmak komut nesnesi ve onu çağırana ilet.

çağıran

çağıran alır komut nesnesi müşteriden ve Onun sadece sorumluluk bir komutu çağırmak (veya çağırmak).

Alıcı

Ardından, alıcı komutu alır ve alınan komuta göre çağrılacak bir yöntem arar.

nasıl görünüyor

Komut kalıbıyla uygulanmadan önce bir veya daha fazla nesnenin kodda nasıl davrandığına dair bir görüntü gördük. Uygulandığında nasıl görüneceği aşağıda açıklanmıştır:

Açıkça büyük ve karmaşık bir nesne, uzun vadede yönetimi daha kolay hale gelebilir, çünkü bir nesnenin diğerine karşı görevleri, bir araya toplanmak yerine kendi özel dünyalarında izole edilmiştir.

Kurallara göre komut nesneleri genellikle aşağıdaki gibi bir ada sahip bir yöntem tanımlar: execute sorumluluğu olan çağırma geleneksel olarak bilinen bir yöntem, çağıran. Yöntemleri tutan nesne genellikle “alıcı”.

Neden komut kalıbına ihtiyacımız var?

Komut desenini kullanmanın en büyük noktası, kodu bölmektir. istiyor olan koddan bir şey yapmak için onu işlemekten sorumlu. Kodunuzun, kodun farklı bölümlerinde bir işlemi birden çok kez gerçekleştirdiğini düşünüyorsanız, onu uygulamaya başlamak iyi bir fikir olabilir. Bunu söyledikten sonra, bu komut nesneleri bize, aşağıdakileri yapabilmek gibi benzersiz durumlar için güzel faydalar sağlar. merkezileştirmek her eylemin/operasyonun ayrı ayrı işlenmesi. Bu, önceki örneğimizde nesnemizin yalnızca bir .eat() komut, bir .jump() komut ve bir .run() emretmek.

Ne zaman kullanılır

Komut kalıbından büyük ölçüde yararlanabileceğiniz bazı örnek durumlar şunlardır:

  • Geri Al / Sıfırla
    • Her eylemin/operasyonun tüm işlenmesi komutlarla merkezileştirildiğinden, bunlar genellikle uygulamalar için geri alma/sıfırlama uygulamak için uygundur.
  • Orijinal istekten bağımsız bir ömre sahip olmak için bir komuta ihtiyacınız var.
  • Ayrıca, sıraya almak istiyorsanız, istekleri farklı zamanlarda belirtin ve yürütün.
  • Geri alma/yineleme işlemlerine ihtiyacınız var. Komutun yürütülmesi, etkilerini tersine çevirmek için saklanabilir. Command sınıfının geri alma ve yineleme yöntemlerini uygulaması önemlidir.
  • İlkel işlemler üzerine kurulu üst düzey işlemler etrafında bir sistem yapılandırmanız gerekir.

Gerçek dünya örneği

Şimdi, zaman içinde yaşlandıkça kurbağaların bir listesini kaydetmenize ve yönetmenize yardımcı olmayı amaçlayan yeni bir kurbağa yöneticisi uygulamasını başlattığımızı farz edelim.

Bu uygulamada, bir Frog sınıfı, buna yardımcı olacak bazı yararlı özellikleri ve yöntemleri somutlaştırıyor:


function createFrog(options) {
  const _opts = {
    name: options.name,
    sex: options.sex,
    age: options.age,
  }

  const foodsEaten = []
  const wordsSpoken = []

  return {
    getOption(key) {
      return _opts[key]
    },
    getFoodsConsumed() {
      return foodsEaten
    },
    getWordsSpoken() {
      return wordsSpoken
    },
    eat(food) {
      console.log(`Frog "${_opts.name}" is eating: ${food.name} (${food.type})`)
      foodsEaten.push(food)
    },
    talk(words) {
      console.log(words)
      wordsSpoken.push(...words)
    },
  }
}

Harika! Şimdi onları somutlaştırarak birden fazla kurbağa yapabiliriz:

const mikeTheFrog = createFrog({ name: 'mike', sex: 'male', age: 1 })
const sallyTheOtherFrog = createFrog({ name: 'sally', sex: 'female', age: 4 })
const michelleTheLastFrog = createFrog({
  name: 'michelle',
  sex: 'female',
  age: 10,
})

Kurbağa uygulamamızı hayata geçirmeye devam edelim:

index.js

const api = {
  fetchFrogs: function() {
    return Promise.resolve([
      { id: 1, name: 'mike', sex: 'male', age: 1 },
      { id: 2, name: 'sally', sex: 'female', age: 2 },
      { id: 3, name: 'michelle', sex: 'female', age: 9 },
    ])
  },
  saveToDb: function(frogs) {
    
    console.log(`Saving ${frogs.length} frogs to our database...`)
    return Promise.resolve()
  },
}

async function init() {
  try {
    const frogs = await api.fetchFrogs()
    return frogs.map((data) => createFrog(data))
  } catch (error) {
    console.error(error)
    throw error
  }
}

function createFrogsManager() {
  const frogs = []

  return {
    addFrog(frog) {
      frogs.push(frog)
      return this
    },
    getFrogs() {
      return frogs
    },
    getMaleFrogs() {
      return frogs.filter((frog) => {
        return frog.getOption('sex') === 'male'
      })
    },
    getFemaleFrogs() {
      return frogs.filter((frog) => {
        return frog.getOption('sex') === 'female'
      })
    },
    feedFrogs(food) {
      frogs.forEach((frog) => {
        frog.eat(food)
      })
      return this
    },
    save: function() {
      return Promise.resolve(api.saveToDb(frogs))
    },
  }
}

function Food(name, type, calories) {
  this.name = name
  this.type = type
  this.calories = calories
}

const fly = new Food('fly', 'insect', 1.5)
const dragonfly = new Food('dragonfly', 'insect', 4)
const mosquito = new Food('mosquito', 'insect', 1.8)
const apple = new Food('apple', 'fruit', 95)

init()
  .then((frogs) => {
    const frogsManager = createFrogsManager()
    
    frogs.forEach((frog) => {
      frogsManager.addFrog(frog)
    })

    const genders = {
      males: frogsManager.getMaleFrogs(),
      females: frogsManager.getFemaleFrogs(),
    }
    
    frogsManager
      .feedFrogs(fly)
      .feedFrogs(mosquito)
      .save()
    console.log(
      'We reached the end and our database is now updated with new data!',
    )
    console.log(
      `Fed: ${genders.males.length} male frogs and ${genders.females.length} female frogs`,
    )
    frogsManager.getFrogs().forEach((frog) => {
      console.log(
        `Frog ${frog.getOption('name')} consumed: ${frog
          .getFoodsConsumed()
          .map((food) => food.name)
          .join(', ')}`,
      )
    })
  })
  .catch((error) => {
    console.error(error)
  })

Sonuç:

komut tasarım desen testi ürün kurbağa kodu

Uygulamamız son derece değerli hale geliyor!

Şimdi yaptığımızı unutmayın olumsuzluk kodda komut tasarım desenini uygulayın – ancak kod mükemmel bir şekilde çalışıyor ve biz Yapabilmek kurbağa uygulamamız daha fazla büyümeyecekse sorun yok.

Şimdi gerçek bir yakından bakalım createFrogsManager api. Bunun, birden fazla kurbağanın faaliyetlerini izlemek için uygun araçlar sağlayarak zaman içinde kurbağaların bir listesini yönetmemiz için bize bir api verdiğini görebiliriz.

Ancak, yakından bakarsanız, gelecekte bizi ısırabilecek bazı potansiyel sorunlar var.

Gördüğümüz ilk şey bizim api’miz createFrogsManager sıkıca birleştirilmiş çalışmak istediğimiz yöntemleri uygulayarak. Sonunda kodumuz bu arabirimi kullanır ve tamamen döndürülen API’ye bağlı olarak yöntemlerini doğrudan çağırır. Bu api, her işlemin hem başlatılmasından hem de işlenmesinden sorumludur.

Örneğin, kullanmamız için döndürülen bu iki yöntemden bahsedelim:

getMaleFrogs() {
  return frogs.filter((frog) => {
    return frog.getOption('sex') === 'male'
  })
},
getFemaleFrogs() {
  return frogs.filter((frog) => {
    return frog.getOption('sex') === 'female'
  })
}

Ya gelecekte her kurbağanın yolunu Cinsiyet biraz değişti?

Yani bunun yerine:

function createFrog(options) {
  const _opts = {
    name: options.name,
    sex: options.sex,
    age: options.age,
  }

  const foodsEaten = []
  const wordsSpoken = []

  return {
    getOption(key) {
      return _opts[key]
    },
    getFoodsConsumed() {
      return foodsEaten
    },
    getWordsSpoken() {
      return wordsSpoken
    },
    eat(food) {
      console.log(`Frog "${_opts.name}" is eating: ${food.name} (${food.type})`)
      foodsEaten.push(food)
    },
    talk(words) {
      console.log(words)
      wordsSpoken.push(...words)
    },
  }
}

Bunun yerine bu oldu:

function createFrog(options) {
  const _opts = {
    name: options.name,
    gender: options.gender,
    age: options.age,
  }

  const foodsEaten = []
  const wordsSpoken = []

  return {
    getOption(key) {
      return _opts[key]
    },
    getFoodsEaten() {
      return foodsEaten
    },
    getWordsSpoken() {
      return wordsSpoken
    },
    eat(food) {
      console.log(`Frog "${_opts.name}" is eating: ${food.name} (${food.type})`)
      foodsEaten.push(food)
    },
    talk(words) {
      console.log(words)
      wordsSpoken.push(...words)
    },
  }
}

Günler geçti ve ortalık sessizleşti. Şikayet bildirimi yok, bu yüzden her şey yolunda olmalı. Sonuçta sunucumuz 7/24 çalışıyor ve kullanıcılar o zamandan beri uygulamamızı kullanıyor.

Ardından, bir müşteri 2 hafta sonra müşteri hizmetleri departmanımızı aradı ve akıllı algoritmalarımızın onları tutmak için doğru kararları vermesine yardımcı olacağına inanarak tüm güvenini bize verdikten sonra tüm kurbağalarının öldüğünü bildirdi ve kaybı için platformumuzu suçladı. uygun şekilde yönetilir.

Geliştiricilerimiz hemen bilgilendirildi ve kodda bu korkunç olayı tetiklemiş olabilecek herhangi bir aksaklık olup olmadığını görmek için durumun hatalarını ayıklamaları istendi.

Daha yakından incelediğimizde, bir test kodu çalıştırdık ve kodumuzun aslında raporlama yaptığını fark ettik. yanlış bilgi!

javascript veri uyumsuzluğunda önceden uygulanmış komut tasarım deseni

Ne?! Mümkün değil!

Geliştiricilerden biri, sorunun şu olduğuna dikkat çekti: .sex kurbağa nesnesinin anahtarı olarak yeniden adlandırıldı .gender!

const _opts = {
  name: options.name,
  gender: options.gender,
  age: options.age,
}

Tekrar normal şekilde çalışması için önceki referansları kullanan kodu anahtarla bulup değiştirmemiz gerekiyordu:

getMaleFrogs() {
  return frogs.filter((frog) => {
    return frog.getOption('gender') === 'male'
  })
},
getFemaleFrogs() {
  return frogs.filter((frog) => {
    return frog.getOption('gender') === 'female'
  })
    }

Oh, ve henüz yakalamadıysanız, kodumuzla ilgili başka bir sorun vardı. yöntem görünüyor getFoodsConsumed içeri createFrog ayrıca değiştirildi getFoodsEaten:

Öncesi:

getFoodsConsumed() {
  return foodsEaten
}

Akım:

getFoodsEaten() {
  return foodsEaten
}

Başka bir senaryoda, ya createFrogsManager api, bazı yöntemlerini yeniden adlandırdı, örneğin .save ile .saveFrogs veya .getFrogs ile .getAllFrogs? Bunun anlamı şudur ki Bu yöntemleri manuel olarak kullanan kodumuzun her bir parçasının güncellenmesi gerekiyor. yeni isimlere!

Dolayısıyla, buradaki örneklerde karşılaştığımız büyük bir sorun, değişiklikten etkilenen tüm kodlarımızı düzeltmemiz gerekmesidir! Saklambaç oyununa dönüşüyor. Ama olması gerekmiyor.

Peki komut deseni bunu tersine çevirmeye nasıl yardımcı olabilir?

Bu yazının başında, komut kalıbının geliştiricilerin şunları yapmasına izin verdiğinden bahsetmiştik. ayırmak olan nesneler rica etmek bir şey uzakta bunlar bu istek İstedikleri yöntemleri çağırmak için.

Ayrıca bu yazının başında yer alacak üç katılımcıdan bahsetmiştik. onlar müşteri, çağıran ve alıcı.

İşte bunun bir temsili:

komut tasarım desen görsel temsil final

Yeniden düzenlememize izin ver createFrogsManager komut yaklaşımını kullanarak:

function createFrogsManager() {
  const frogs = []

  return {
    execute(command, ...args) {
      return command.execute(frogs, ...args)
    },
  }
}

Gerçekten ihtiyacımız olan tek şey bu çünkü komutlar işini yap.

Devam edeceğiz ve Command api’nin her yöntemi için somut komutlar oluşturmak için kullanacağımız yapıcı:

function Command(execute) {
  this.execute = execute
}

Her şey hallolduğuna göre, devam edelim ve somut komutları verelim:

function AddFrogCommand(frog) {
  return new Command(function(frogs) {
    frogs.push(frog)
  })
}

function GetFrogsCommand() {
  return new Command(function(frogs) {
    return frogs
  })
}

function FeedFrogsCommand(food) {
  return new Command(function(frogs) {
    frogs.forEach((frog) => {
      frog.eat(food)
    })
  })
}

function SaveCommand() {
  return new Command(function(frogs) {
    api.saveToDb(
      frogs.map((frog) => ({
        name: frog.name,
        gender: frog.gender,
        age: frog.age,
      })),
    )
  })
}

Bu yerindeyken, onu şu şekilde kullanabiliriz:

function Food(name, type, calories) {
  this.name = name
  this.type = type
  this.calories = calories
}

const mikeTheFrog = createFrog({
  name: 'mike',
  gender: 'male',
  age: 2,
})

const sallyTheFrog = createFrog({
  name: 'sally',
  gender: 'female',
  age: 1,
})

const frogsManager = createFrogsManager()
frogsManager.execute(new AddFrogCommand(mikeTheFrog))
frogsManager.execute(new FeedFrogsCommand(new Food('apple', 'fruit', 95)))
frogsManager.execute(new FeedFrogsCommand(new Food('fly', 'insect', 1)))
frogsManager.execute(new AddFrogCommand(sallyTheFrog))
frogsManager.execute(new SaveCommand())
const updatedFrogs = frogsManager.execute(new GetFrogsCommand())

Sonuç:

kurbağaları veritabanına kaydeden komut tasarım deseni

JavaScript’te olduğu için görselde alıcının boş olduğunu belirtmek isterim. tüm fonksiyonlar ve nesneler temelde komutların kendileridir, .execute komutları doğrudan çağırarak:

function createFrogsManager() {
  const frogs = []

  return {
    execute(command, ...args) {
      return command.execute(frogs, ...args)
    },
  }
}

Çözüm

Ve bu yazının sonu burada bitiyor! Umarım bunu değerli bulmuşsunuzdur ve gelecekte daha fazlasını ararsınız!

Bir cevap yazın

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