Javscript

JavaScript’te Fabrika Tasarım Modelinin Gücü – JSManifest

Programcılar olarak kod yazarken her zaman iyi kararlar vermeye çalışıyoruz. Özellikle kodumuz zamanla büyüdüğünde, bu her zaman kolay bir iş değildir. Neyse ki, doğru fırsat geldiğinde bir uygulamayı diğerine tercih etmenin kanıtlanmış yolları var.

Programlamada yeniyseniz, henüz karmaşık bir nesneye sahip olduğunuz ve karmaşıklıkları soyutlamak için Fabrika modelini kullanmanız gereken bir durumla karşılaşmamış olabilirsiniz. Geleceğinize kod yazarak devam etmeyi planlıyorsanız, bu yazı size yardımcı olacaktır.

Bu yazıda, gereksiz karmaşıklığı önlemek için karmaşık bir nesneyi daha basit nesnelere ayırmanın bir yolu olan JavaScript’te Fabrika Tasarım Modelinin Gücünü gözden geçireceğiz. En iyi uygulama olarak DRY ilkesini izleyeceğimizi unutmayın.

Gerçek dünyada bir fabrika deyince aklımıza bir şeyler yapan bir laboratuvar geliyor. Bunu koda çevirdiğimizde fabrika modeli tam olarak budur.

Diyelim ki bir MMORPG oyunu yapıyormuşuz gibi, bu kalıptan yararlanan bölümlerin üzerinden geçeceğiz ve uygulamalarımıza nasıl fayda sağlayacağını göreceğiz.

sahip olacağız Game a sınıfı Profile kullanıcılar yazılımımızı açtığında profiller oluşturmak ve profillerin kullanıcılarımızın seçmesi için karakter olarak oluşturacağı dört sınıf:

class Mag extends Character {}
class Thief extends Character {}
class Archer extends Character {}
class Warrior extends Character {}

class Profile {
  constructor(name, email = '') {
    this.name = name
    this.email = email
  }

  createCharacter(classType) {
    switch (classType) {
      case 'archer':
        this.character = new Archer()
        return this.character
      case 'mage':
        this.character = new Mage()
        return this.character
      case 'thief':
        this.character = new Thief()
        return this.character
      case 'warrior':
        this.character = new Warrior()
        return this.character
      default:
        throw new Error(
          `Invalid class type "${classType}". Choose one of: "archer", "mage", "thief", or "warrior"`,
        )
    }
  }

  synchronizeProfileContacts(anotherProfile) {
    
  }

  setName(name) {
    this.name = name
  }

  setEmail(email) {
    this.email = email
  }
}

class Game {
  constructor() {
    this.users = {}
  }

  createUser(name) {
    const user = new Profile(name)
    this.users[user.id] = user
    return user
  }
}

const game = new Game()
const bobsProfile = game.createUser('bob')
const bobsMage = bobsProfile.create('mage')

Üç ay sonra, adında başka bir karakter sınıfı uygulamak istediğimize karar veriyoruz. Shaman.

Bunu yapmak için sınıfı oluşturmamız gerekiyor:

class Shaman extends Character {}

Kullanıcıların seçim yapmasına izin vermek istediğimizde Shaman güncelleme ve çağrıdan sonra sınıf profile.createCharacter şu hatayı alacağız:

Error: Invalid class type "shaman". Choose one of: "archer", "mage", "thief", or "warrior"

Bunun nedeni, değiştirmemiz gereken create üzerindeki yöntem Profile sınıf.

Bunu buna değiştirdikten sonra çalışacaktır:

class Profile {
  constructor(name, email = '') {
    this.name = name
    this.email = email
  }

  createCharacter(classType) {
    switch (classType) {
      case 'archer':
        this.character = new Archer()
        return this.character
      case 'mage':
        this.character = new Mage()
        return this.character
      case 'shaman':
        this.character = new Shaman()
        return this.character
      case 'thief':
        this.character = new Thief()
        return this.character
      case 'warrior':
        this.character = new Warrior()
        return this.character
      default:
        throw new Error(
          `Invalid class type "${classType}". Choose one of: "archer", "mage", "shaman", "thief", or "warrior"`,
        )
    }
  }

  synchronizeProfileContacts(anotherProfile) {
    
  }

  setName(name) {
    this.name = name
  }

  setEmail(email) {
    this.email = email
  }
}

Fabrika tasarım modelinin çözdüğü sorun budur.

Ya 3 karakter sınıfı daha eklemek istersek? Uygulamayı 1-3 kez değiştirmemiz gerekiyor.

Her geliştiricinin yapması gerektiği gibi DRY ilkesini takip edeceğimizden bahsettiğimizi hatırlıyor musunuz? Bu, bu kuralı ihlal ediyor!

Programlamada yeniyseniz, yalnızca şu anda sahip olduğumuz koda bakılırsa, bu kulağa çok büyük bir şey gibi gelmiyor. çünkü bizim Game sınıf sadece bir createUser Ancak gerçek dünyada MMORPG oyunları, kullanıcıları için eğlence için daha değerli kılan gerekli tüm özellikler nedeniyle kod boyutunda kesinlikle çok daha fazla büyüyor.

Bizim Game sınıfının birçok özelliği uygulamak için gereken tonlarca farklı yöntemi olabilir, örneğin createTerrain, createEquipment, createMonster, createAttack, createPotion, createRaid, createBuilding, createShopvb.

Ne yazık ki bu yöntemlerin her biri büyük olasılıkla daha da genişletilmesi gerekiyor çünkü her birinin farklı türler oluşturması gerekecek. örneğin createEquipment Kılıç ve bot türü gibi daha fazla tür çeşidi üretmesi gereken kılıç ekipmanı, asa, bot, zırh oluşturmak için bir yol uygulaması gerekebilir.

Yani eğer hepsini şimdi uygulamak istiyorsak, her yöntemi aynen ilk yazdığımızda yaptığımız gibi değiştirmemiz gerekiyor. Shaman sınıf ve zaten ilk hatamızın acısını çektik çünkü uygulamamızda Şaman’ı eklemeyi unuttuk Profile.createUser yöntem.

Buradaki fabrikaları durdurursak, üç ay sonra bu hızla bunaltıcı hale gelecek çünkü her yönteme atlamak ve onları değiştirmek zorunda kalıyoruz.

Kod büyüdükçe fabrika modelinin parladığı yer burasıdır.

Farzedelim Profile.createCharacter Bir daha dokunmak zorunda kalmamak için değişmeden kalabilir mi? hangisi olduğunu bilmesine gerek yok tip veya tür oluşturduğu karakter sınıfı. Sadece bir karakter sınıfı verilmesi ve onu örneğinde saklaması gerekiyor.

10 karakter sınıfı daha eklemek istiyorsak, aynı işlevi manuel olarak bulup güncellememiz gerekir. Profile ne tür karakter sınıflarının üretildiğini umursamıyor çünkü yalnızca aşağıdaki gibi yöntemlerle ilgileniyor setName ve synchronizeProfileContacts.

Yapabiliriz Öz o kısmı dışarı ve içine koy fabrika ile üretmek şunlar nesneler yerine:

class CharacterClassCreator {
  create(classType) {
    switch (classType) {
      case 'archer':
        return new Archer()
      case 'mage':
        return new Mage()
      case 'shaman':
        return new Shaman()
      case 'thief':
        return new Thief()
      case 'warrior':
        return new Warrior()
      default:
        throw new Error(
          `Invalid class type "${classType}". Choose one of: "archer", "mage", "thief", or "warrior"`,
        )
    }
  }
}

Bizim Profile class bu değişikliğe uyum sağlamak için daha zarif görünebilir:

class Profile {
  constructor(name, email = '') {
    this.name = name
    this.email = email
  }

  synchronizeProfileContacts(anotherProfile) {
    
  }

  setName(name) {
    this.name = name
  }

  setEmail(email) {
    this.email = email
  }

  setCharacter(character) {
    this.character = character
  }
}

Artık DRY ilkesini ihlal etmiyoruz. Yaşasın! Sadece değişmemiz gerekiyor CharacterClassCreator oluşturmak için daha fazla karakter sınıfı uygulamak istiyorsak. Farklı karakter sınıfı nesneleri üretmek için belirlediğimiz tek sorumluluk budur.

İşte fabrikadan önce sahip olduğumuz şeyin bir görseli:

Ve bu ne Profile şimdi benziyor:

profil-fabrika-tasarım-desen-gücü ile-oluşturma

Harika! Profilin güzel ve temiz görünmesini sağladık. etkinleştirdik Profile sadece mantığına odaklanmak için sınıf.

nerede olduğunu merak ediyorsanız CharacterClassCreator burada duruyor, aslında perde arkasında olan şey bu:

profil-karakterli-oluşturma-sınıfı-yaratıcı-fabrika

Bunun yerine karakter sınıfları oluşturma mantığını işlemek için bir orta adam (fabrika) ekledik. Şu andan itibaren, uygulamayı bu koda güncellememiz gerektiğinde, yalnızca CharacterCreationClass.

Umarım bu aşamada faydasını fark etmeye başlayabilirsiniz. Diğer yöntemlerden bahsettiğimizde hatırla Game sınıf sonunda gibi olacak createBuilding ve createTerrain? Hepsine benzer bir fabrika yaklaşımı uygularsak aynı süreç olur. Bu, bu sınıfların her birinin kendi mantığına odaklanmasını sağlar.

Kodumuzla devam edelim.

MMORPG oyunlarında farklı karakter sınıfları farklı donanımlar giyiyor.

Örneğin, sihirbazlar genellikle değnek kullanır, savaşçılar ağır çelik zırh giyer ve kılıç taşır, hırsızlar bir veya iki hançer taşır ve okçular tatar yayı kullanır.

Buna ek olarak, kullanıcılar bir hesap açtıklarında ve bununla birlikte bir tür üyelik satın aldıklarında genellikle bazı avantajlar vardır.

İşte böyle görünebilir:

class Equipment {
  constructor(name) {
    this.name = name
  }
}

class CharacterClassCreator {
  async applyMembershipCode(code) {
    
    
    
    return { equipments: [{ type: 'ore' }] }
  }

  async create(profile, classType) {
    const creatorMap = {
      archer: {
        Class: Archer,
      },
      mage: {
        Class: Mage,
      },
      shaman: {
        Class: Shaman,
      },
      thief: {
        Class: Thief,
      },
      warrior: {
        Class: Warrior,
      },
    }

    let character
    
    let starterWeapon

    if (creatorMap[classType]) {
      const { Class, membership } = creatorMap[classType]
      character = new Class()

      if (character instanceof Archer) {
        starterWeapon = new Equipment('crossbow')
      } else if (character instanceof Mage) {
        starterWeapon = new Equipment('staff')
      } else if (character instanceof Shaman) {
        starterWeapon = new Equipment('claw')
      } else if (character instanceof Thief) {
        starterWeapon = [new Equipment('dagger'), new Equipment('dagger')]
      } else if (character instanceof Warrior) {
        starterWeapon = new Equipment('sword')
      }

      character.useEquipment(starterWeapon)

      if (typeof profile.code === 'number') {
        if (profile.code) {
          const { equipments: _equipments_ } = await this.applyMembershipCode(
            profile.code,
          )
          
          
          _equipments_.forEach((equipment) => {
            
            if (Array.isArray(equipment)) {
              character.useEquipment(equipment[0])
              character.useEquipment(equipment[1])
            } else {
              character.useEquipment(equipment)
            }

            if (membership) {
              if (membership.status === 'gold') {
                
                if (membership.accessories) {
                  membership.accessories.forEach(({ accessory }) => {
                    if (accessory.type === 'ore') {
                      
                      const { succeeded, equipment } = this.applyEnhancement(
                        starterWeapon,
                        accessory,
                      )
                      if (succeeded) starterWeapon = equipment
                    } else if (accessory.type === 'fun-wear') {
                      
                      character.useEquipment(new Equipment(accessory.name))
                    }
                  })
                }
              }
            }
          })
        }
      }
    } else {
      throw new Error(
        `Invalid class type "${classType}". Choose one of: "archer", "mage", "shaman", "thief", or "warrior"`,
      )
    }

    return character
  }

  applyEnhancement(equipment, ore) {
    
    
    return { equipment, succeeded: true }
  }
}

bizim gibi görünüyor CharacterClassCreator.create yöntem biraz karmaşık hale geliyor. KURU ilkesini ihlal etmeye geri döndük.

Ama fazla seçeneğimiz yoktu çünkü onu koymak mantıklı değil. Profileve biz buna sahip olmak istemiyoruz Game çünkü Game sahip olacak bolca yüksek düzeyde kapsamında olması gereken zaman içinde yöntemlerin. Ayrıca küresel kapsamda sadece sabit kodlayamayız. Bu, programımızı çok hataya açık hale getirecektir. Küresel kapsamı kirletiyor olacağız ve kodumuzun daha fazla uzantılarının küresel kapsamı içermesi gerekecek.

Artık karakter sınıfını oluşturmaktan sorumlu olmalı, başlangıç ​​silahının oluşturulmasını sağlamalıdır. ve karaktere ekleyin, (varsa) kullanıcının üyeliğiyle satın aldığı üyelik ayrıcalıklarını uygulayın, yeni karakteriyle devam edin, satın aldığı aksesuarın türünü kontrol edin (MMORPG’mizde ideal olarak kaç farklı aksesuar türü olacağını düşünmeyelim) sonraki birkaç yıl) tam olarak ödediklerini aldıklarından emin olmak için (bu durumda bir güçlendirici işlevi çalıştırarak), bu geliştirmeyi başlangıç ​​silahına ekleyerek, geliştirilmişse başlangıç ​​silahını değiştirin ve hatta asenkron oldu!

Ya bunu bir kütüphane olarak yayınlarsak? Her geliştiricinin programı şimdi bozulacak çünkü bir profile parametremizde ilk parametre olarak CharacterClassCreator asenkron olacak şekilde dönüştürme ile birlikte sınıf.

Tüm bunları sadece bir karakter sınıfı oluşturmak için yapmak zorunda kalmak, bizim için çok bunaltıcı. CharacterClassCreator aşağıda gösterildiği gibi sınıf:

javascript-kötü kod-uygulamadan önce-fabrika-modeli

Pekala, daha fazla fabrika uygulayabilir ve kendi mantıklarını işleyen bu nesneleri yaratma sorumluluklarını devredebiliriz.

Genişletilmiş kodu göndereceğim ve bu sorunlardan bazılarını çözmek için birkaç fabrika uygularken soyutlamanın nasıl göründüğünün bir diyagramını göstereceğim:

class Character {
  useEquipment() {}
}

class Mage extends Character {}
class Shaman extends Character {}
class Thief extends Character {}
class Archer extends Character {}
class Warrior extends Character {}

class Profile {
  constructor(name, email = '') {
    this.initializer = new ProfileInitializer()
    this.id = Math.random().toString(36).substring(2, 9)
    this.name = name
    this.email = email
  }

  async initialize() {
    await this.initializer.initialize(this)
  }

  synchronizeProfileContacts(anotherProfile) {
    
  }

  setName(name) {
    this.name = name
  }

  setEmail(email) {
    this.email = email
  }

  setCharacter(character) {
    this.character = character
  }

  setMembership(membership) {
    this.membership = membership
  }
}

class Equipment {
  constructor(name) {
    this.name = name
  }
}

class CharacterClassCreator {
  create(classType) {
    const creatorMap = {
      archer: {
        Class: Archer,
      },
      mage: {
        Class: Mage,
      },
      shaman: {
        Class: Shaman,
      },
      thief: {
        Class: Thief,
      },
      warrior: {
        Class: Warrior,
      },
    }

    let character

    if (creatorMap[classType]) {
      const { Class } = creatorMap[classType]
      character = new Class()
      return character
    } else {
      throw new Error(
        `Invalid class type "${classType}". Choose one of: "archer", "mage", "shaman", "thief", or "warrior"`,
      )
    }
  }
}

class Membership {
  constructor(type) {
    this.type = type
  }

  async applyMembershipCode(profile, code) {
    
    
    
    return { equipments: [{ type: 'ore' }] }
  }
}

class MembershipFactory {
  create(type) {
    const membership = new Membership(type)
    return membership
  }
}

class ProfileInitializer {
  constructor() {
    this.initializers = {}
  }

  async initialize(profile) {
    for (const [name, initialize] of Object.entries(this.initializers)) {
      const initialize = profile.initializers[name]
      await initialize(profile.character)
    }
    return profile.character
  }

  use(name, callback) {
    this.initializers[name] = callback
  }
}

class EquipmentEnhancer {
  applyEnhancement(equipment, ore) {
    
    
    return { equipment, succeeded: true }
  }
}

class Game {
  constructor() {
    this.users = {}
  }

  createUser(name) {
    const user = new Profile(name)
    this.users[user.id] = user
    return user
  }
}

;(async () => {
  const characterClassCreator = new CharacterClassCreator()
  const profileInitializer = new ProfileInitializer()
  const equipmentEnhancer = new EquipmentEnhancer()
  const membershipFactory = new MembershipFactory()

  const game = new Game()

  
  profileInitializer.use(async (profile) => {
    let character = profile.character
    let starterWeapon

    if (character instanceof Archer) {
      starterWeapon = new Equipment('crossbow')
    } else if (character instanceof Mage) {
      starterWeapon = new Equipment('staff')
    } else if (character instanceof Shaman) {
      starterWeapon = new Equipment('claw')
    } else if (character instanceof Thief) {
      starterWeapon = [new Equipment('dagger'), new Equipment('dagger')]
    } else if (character instanceof Warrior) {
      starterWeapon = new Equipment('sword')
    }

    character.useEquipment(starterWeapon)
  })

  
  profileInitializer.use(async (profile) => {
    const character = profile.character

    switch (profile.code) {
      case 12512: {
        
        
        const goldMembership = membershipFactory.create('gold')

        profile.setMembership(goldMembership)

        const { equipments: _equipments_ } =
          await profile.membership.applyMembershipCode(profile.code)
        
        
        _equipments_.forEach((equipment) => {
          
          if (Array.isArray(equipment)) {
            character.useEquipment(equipment[0])
            character.useEquipment(equipment[1])
          } else {
            character.useEquipment(equipment)
          }

          if (profile.membership.accessories) {
            profile.membership.accessories.forEach(({ accessory }) => {
              if (accessory.type === 'ore') {
                
                const { succeeded, equipment } =
                  equipmentEnhancer.applyEnhancement(starterWeapon, accessory)
                if (succeeded) starterWeapon = equipment
              } else if (accessory.type === 'fun-wear') {
                
                character.useEquipment(new Equipment(accessory.name))
              }
            })
          }
        })
        break
      }
      default:
        break
    }
  })

  const bobsProfile = game.createUser('bob')
  
  const bobsCharacter = await characterClassCreator.create('shaman')

  console.log(game)
  console.log(bobsProfile)
  console.log(bobsCharacter)
})()

Ve işte nasıl göründüğüne dair bir görsel:

javascript-karmaşıklık-fabrika-tasarım-desen-sonucu azaltılmış

Artık fabrikanın daha mantıklı olduğu bazı karmaşıklıkları soyutladığını açıkça görebiliyoruz.

Her sınıf nesnesinin kendi sorumluluğu vardır. Bu gönderideki örnekleri incelerken temel endişemiz, kodumuzun en hassas kısmı olan profili başlatmaktır. Profilin basit kalmasını ve fabrikaların ne tür üyeliklerin uygulandığı gibi soyutlamaları ele almasına izin vermesini istiyoruz. nasıl Onlar davranıyorlar. Profile sadece profilin tüm parçaları ayarlamak için ihtiyaç duyduğu arayüze sahip olmasını sağlama konusunda endişeleniyor.

Çözüm

Okuduğunuz için teşekkür ederim ve gelecekte benden daha kaliteli yazılar gelmesini sabırsızlıkla bekliyoruz!

Yazılarıma e-posta ile abone olun 🙂

İlgili Makaleler

Bir cevap yazın

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

Başa dön tuşu