Javscript

JavaScript’te Sorumluluk Zincirinin Gücü – JSManifest

Tasarım Modelleri, kurumsal uygulamalarda gerçek hayat problemlerini çözdüğü kanıtlanmış olduğu için yazılım endüstrisinde bilinmesi gereken bir bütündür. Yaygın olarak kullanılan bir kalıp, yayıncılıkta yaygın olarak kullanılan yayınla/abone ol kalıbıdır. DOM. Komut deseni şurada kullanılır: redux kolay ve öngörülebilir bir şekilde ölçeklenebilirken uygulama durumunu yönetme konusundaki sağlam yeteneği nedeniyle kısa sürede (bu güne kadar) patladı.

Tasarım kalıplarının ilginç yanı, bunların “herkese uyan tek beden” bir çözüm olmamasıdır. Bazı tasarım desenleri, senaryoya bağlı olarak diğerlerinden daha verimli çalışabilir ve bunların uygulamalarında ne zaman ve nasıl daha verimli kullanılacağına geliştirici karar verir.

Bu yazıda, birini uygulayarak Sorumluluk Zinciri tasarım modelini gözden geçireceğiz ve bazı kullanım durumları hakkında konuşacağız.

Sorumluluk Zinciri (COR), bazı isteklerin birden çok nesne tarafından gönderilmesine, alınmasına ve işlenmesine izin veren bir kalıptır. Bu nesneler (sadece işlevler), önceki veya sonraki isteğin uygulama ayrıntılarına bağlı değildir ve yürütmeyi çalıştırdığında ne yapılacağına karar verebilir. Ayrıca tüm zinciri iptal edebilir veya isteğin zincirdeki bir sonraki nesneye (veya işleve) devam etmesine izin verebilirler.

DOM’da kullanılan desene bir örnek:

<div id="root" onclick="onBtnContainerClick()">
  <button>Say hello</button>
</div>
const btn = document.querySelector('button')

btn.addEventListener('click', function sayHello(e) {
  console.log('hello!!')
})

function onBtnContainerClick() {
  console.log('Clicked btnContainer')
}

Butona tıkladığınızda butona eklenen dinleyici çağrılır. İşlev sona erdiğinde, “istek” iletilir (veya kabarcıklı) ebeveynine:

Bunu arayarak ebeveynine devam etmesini (veya köpürmesini) durdurmaya karar verebilir. e.stopPropagation():

sorumluluk zinciri-dom2

const btn = document.querySelector('button')

btn.addEventListener('click', function sayHello(e) {
  e.preventDefault()
  console.log('hello!!')
})

function onBtnContainerClick() {
  console.log('Clicked btnContainer')
}

İşlevler birbiri ardına kontrol edilebilir ve öngörülebilir bir şekilde zincirlenebildiği sürece, bu modeli uygulamanın gerçekten “resmi” veya doğru bir yolu yoktur. Genel olarak, onu tanımlayan ve bahsetmeye değer olan iki ana rol vardır:

  1. işleyici – İsteklerin işlendiği arayüzü belirler. Ayrıca zincirde birbirine bağlı istekleri de ele alır.
  2. Müşteri – Zincirdeki bir işleyiciye istek başlatır

İşte bir özden alınan modelin bir varyasyonu:

class CumulativeSum {
  constructor(intialValue = 0) {
    this.sum = intialValue
  }

  add(value) {
    this.sum += value
    return this
  }
}

Kaynak

Zincir, aşağıdaki durumlarda oluşur: add fonksiyon döndürür this:

const sum1 = new CumulativeSum()
console.log(sum1.add(10).add(2).add(50).sum) 

Bu, çağrının sonunda örnek tekrar döndürüldüğünden, istenildiği kadar yeniden çağrılabileceği anlamına gelir:

add(value) {
  this.sum += value;
  return this;
}

Zincirin nasıl kırılacağı veya devam ettirileceği sorumluluğu herkese verilir. add fonksiyon bloğu. Örneğimizde bir tane var add değerini bir şekilde toplayan yöntem:

içinde ne olur add yöntem tam anlamıyla olabilir herhangi bir şey ve işlev kapsamının dışında kalan hiçbir şeye bağlı olması gerekmez. Örneğin, zincirdeki her talebin kendi mantığını kullanarak yeni bir toplamı hesaplayabildiği ve önceki toplamın sonucunu zincirdeki ilgili sonraki fonksiyona iletebildiği bir API istekleri zinciri ile bunu kullanabiliriz. onunla çalışmak.

Hayali depomuzdan evcil hayvan sattığımız bir durumda olduğumuzu farz edin. Eyalet çapında acil bir evde kalma emri nedeniyle onları normalden daha erken stoklamamız gerekip gerekmediğini belirleyebilmemiz için depomuzda satışta olan tüm evcil hayvanların toplamını almamız gerekiyor. Bunu yapıyoruz çünkü insanlar büyük ihtimalle yalnızlık ve depresyondan mustarip olacak, bu yüzden önümüzdeki birkaç ay satışların patlamasını bekliyoruz:

class CumulativeSum {
  constructor() {
    this._executor = null
  }

  get executor() {
    if (!this._executor) return (val) => val
    return this._executor
  }

  set executor(fn) {
    this._executor = fn
  }

  async add(currentValue) {
    const result = await this.executor(currentValue, async (nextVal) => {
      if (this.next) return this.next.add(nextVal)
      return nextVal
    })
    return result
  }
}

bizim genişletilmiş CumulativeSum aynı hedefe ulaşma yeteneğini koruyor, ancak şimdi davranışı özelleştirmemize izin veriyor. nasıl sonraki toplamı belirlemek için.

Aşağıda bir PetSum toplamı tutan sınıf. kendi olacak add herhangi bir başlatan yöntem CumulativeSum onları arayarak add yöntemi (bu, kendisinden sonra bağlanan tüm istek zincirini arayacaktır). Yazının başında hatırlarsanız “işleyici“bizim PetSum aşağıda:

class PetsSum {
  constructor() {
    this.totalPets = 0
  }

  async add(summee) {
    this.totalPets = await summee.add(this.totalPets)
    return this.totalPets
  }
}

const reqSum = new ReqSum()

Ve işte burada bir örnek 4 CumulativeSumDepomuzda birden fazla tür bulunduğundan, her biri farklı bir hayvan türünü temsil eden birbirine bağlanacak ‘ler oluşturulur:

const catsStorage = new CumulativeSum()
const dogsStorage = new CumulativeSum()
const squirrelsStorage = new CumulativeSum()
const birdsStorage = new CumulativeSum()

Her biri, türlerinin toplamını hesaplamak için kendi yöntemlerine sahip olabilir veya hatta hiçbir şey yapmamayı seçebilir:

catsStorage.executor = async (val, next) => {
  const catsUrl = new URL('https://catstorage.com/api/catcount')
  
  const totalCats = 3
  if (next) return next(val + totalCats)
  return val + totalCats
}

dogsStorage.executor = async (val, next) => {
  const totalDogs = 2

  if (next) return next(val + totalDogs)
  return val + totalDogs
}

squirrelsStorage.executor = async (val, next) => {
  const totalSquirrels = 10
  if (next) return next(val + totalSquirrels)
  return val + totalSquirrels
}

birdsStorage.executor = async (val, next) => {
  const totalBirds = 1
  if (next) return next(val + totalBirds)
  return val + totalBirds
}

catsStorage.next = dogsStorage
dogsStorage.next = squirrelsStorage
squirrelsStorage.next = birdsStorage

Toplamı elde etmek için yapılması gereken tek şey add örneklerden birinde yöntem:

const getTotalPets = async (summee) => petSum.add(summee)

getTotalPets(catsStorage)
  .then((totalPets) => {
    console.log(`Total pets now: ${totalPets}`)
    
  })
  .catch(console.error)

Ne kadar kolay olduğunu gördün mü?

İstek zincirini aradaki örneklerden birinden başlatabiliriz, örneğin köpeklerle başlayıp kedileri unutabiliriz (unutmayın). catsStorage ilk istek buydu, ancak bununla başlayabiliriz dogsStorage ve oradan istek zincirine devam edin):

const getTotalPets = async (summee) => petSum.add(summee)

getTotalPets(dogsStorage)
  .then((totalPets) => {
    console.log(`Total pets now: ${totalPets}`)
    
  })
  .catch(console.error)

Bu, bir müşterinin 23 Mart gibi belirli bir günde 13:00 ile 14:00 arasında tüm sincaplarımızı satın almak için randevu alması gibi, istediğimiz zaman birini veya diğerini kapatmak için geçiş yapmak istediğimizde yararlıdır:

const isTodayThatDayBetween1and2PM = (date) => {
  
  return true
}

const getTotalPets = async (summee) => {
  if (isTodayThatDayBetween1and2PM()) {
    dogsStorage.next = birdsStorage
  }
  return petSum.add(summee)
}

getTotalPets(catsStorage)
  .then((totalPets) => {
    console.log(`Total pets now: ${totalPets}`)
    
  })
  .catch(console.error)

aşina iseniz Bağlantılı liste veri yapısı, çarpıcı biçimde benzer olduklarını fark edebilirsiniz. Ve onlar! Bağlantılı liste ile yapabileceğiniz herhangi bir işlem de örneklerimizde yapılabilmektedir. Geriye doğru dahil olmak üzere zincirdeki her işlevi, a gibi bir şey ekleyerek de geçebiliriz. prev (önceki için) her birinde mülk:

class PetsSum {
  constructor() {
    this.totalPets = 0
  }

  async add(summee) {
    this.totalPets = await summee.add(this.totalPets)
    return this.totalPets
  }

  petSum.forEach(summee, callback) {
    let currIndex = 0
    let currSummee = summee

    while (currSummee) {
      callback(currSummee, currIndex)
      currSummee = currSummee.next
      currIndex++
    }
  }
}

const catsStorage = new CumulativeSum('Cats')
const dogsStorage = new CumulativeSum('Dogs')
const squirrelsStorage = new CumulativeSum('Squirrels')
const birdsStorage = new CumulativeSum('Birds')
const frogsStorage = new CumulativeSum('Frogs')
const rabbitsStorage = new CumulativeSum('Rabbits')
const fishStorage = new CumulativeSum('Fishes')

catsStorage.next = dogsStorage
dogsStorage.next = squirrelsStorage
squirrelsStorage.next = birdsStorage
birdsStorage.next = frogsStorage
frogsStorage.next = rabbitsStorage
rabbitsStorage.next = fishStorage

forEach(catsStorage, (summee, index) => {
  console.log([summee.name, index])
})

Sonuç:

her sorumluluk zinciri için

Bu hangi gerçek dünya senaryolarında kullanılır?

İşte COR modelinin güzel bir şekilde uyduğu bazı senaryolar:

  1. Kahve makinesi – Bir kahve makinesi, zincirdeki her talebin/işleyicinin makineye eklenecek ayrı bir bileşen tanımladığı COR modeliyle programlanabilir. Akış, her an malzeme eklemeyi bırakabileceğimiz veya espresso, süt, badem kırıntısı sosları vb. ile devam etmeyi seçebileceğimiz günümüzde zaten nasıl kahve yaptığımızı yakından takip ediyor.

  2. ATM makinesi – 20$’lık banknotlarda 100$’ı çekme seçeneği seçildiğinde, makine, işlemin bittiği yerde istenen miktara ulaşana kadar beş adet 20$’lık banknot dağıtır. Bu davranış şu durumlarda gerçekleştirilebilir:

  3. transformatörler – Bazı özel AST’lerde bir transformatör/ayrıştırıcı zinciri kullanın (bu makaleyi yazarken tam anlamıyla bunu yazdım, bu yüzden bu tam anlamıyla savaşta test edilmiş veya optimize edilmiş bir kod değil, ancak kodu çalıştırdım ve güzel çalışıyor):

const handleColors = (obj, key, value, next) => {
  if (typeof value === 'string' && value.startsWith('0x')) {
    obj[key] = value.replace('0x', '#')
  }
  next()
}

const handleSizes = (obj, key, value, next) => {
  if (/(width|height|fontSize)/.test(key)) {
    const isDec = Number(value) % 1 !== 0
    obj[key] = String(value).endsWith('px')
      ? value
      : `${String(isDec ? value * 100 : value)}px`
  }
  next()
}

const handleAlign = (obj, key, value, next) => {
  switch (key) {
    case 'align':
      if (value === 'centerX') {
        obj.textAlign = 'center'
        delete obj.align
      }
      break
    case 'textAlign':
      if (value !== null && typeof value === 'object') {
        if (value.y) {
          obj.display = 'flex'
          if (value.y === 'center') {
            Object.assign(obj, { display: 'flex', alignItems: 'center' })
            if (value.x === 'center') {
              obj.justifyContent = 'center'
            }
            if (value.y === 'bottom') {
              obj.justifyContent = 'flex-end'
            }
          }
          if (value.x === 'center') obj.textAlign = 'center'
          else delete value.textAlign
        }
      }
      break
  }
  next()
}

class TNode {
  next = null

  constructor(callback) {
    this.callback = callback
  }

  execute(...args) {
    return this.callback(...args, () => {
      this.next && this.next.execute(...args)
    })
  }
}

function createTransform(...transformers) {
  function withNode(transformer) {
    return new TNode(transformer)
  }

  transformers = transformers.slice().reverse()

  let executor = withNode(transformers.pop())
  let curr = executor

  while (transformers.length) {
    curr.next = withNode(transformers.pop())
    curr = curr.next
  }

  function transform(obj) {
    for (const [key, value] of Object.entries(obj)) {
      executor.execute(obj, key, value)
    }
    return obj
  }

  return transform
}

const rawData = {
  backgroundColor: '0x020303',
  color: '0x201313',
  fontSize: '14',
  width: '0.9',
  height: '0.50',
  textAlign: {
    x: 'center',
    y: 'center',
  },
}

const transform = createTransform(handleColors, handleSizes, handleAlign)

const result = transform(rawData)

console.log(result)

Sonuç:

{
  "backgroundColor": "#020303",
  "color": "#201313",
  "fontSize": "14px",
  "width": "90px",
  "height": "50px",
  "textAlign": "center",
  "display": "flex",
  "alignItems": "center",
  "justifyContent": "center"
}

Tüm veya transformatör fonksiyonlarında diyoruz next zincirdeki bir sonraki işlevi çağırmak için oluşturduğumuz bir işlev olan geri arama.

Fonksiyonun içinde istediğimiz zaman zincire devam etmemeyi seçebiliriz. Örneğin, önce durabiliriz handleAlign eğer fonksiyon çağrılır textAlign bir nesne değil (bir nesne, bir DOM öğesinde geçersiz olur):

const handleSizes = (obj, key, value, next) => {
  if (/(width|height|fontSize)/.test(key)) {
    const isDec = Number(value) % 1 !== 0
    obj[key] = String(value).endsWith('px')
      ? value
      : `${String(isDec ? value * 100 : value)}px`
  }
  if (typeof obj.textAlign === 'object') {
    next()
  }
}

Çö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!

İlgili Makaleler

Bir cevap yazın

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

Başa dön tuşu