Pola desain struktural dalam ES6 + pada contoh Game of Thrones



Selamat siang teman!

Pola desain struktural digunakan untuk membangun sistem besar hubungan antar objek untuk menjaga fleksibilitas dan efisiensi. Mari kita lihat beberapa dari mereka dengan referensi ke Game of Thrones.

Dalam pengembangan perangkat lunak, pola desain digunakan untuk memecahkan masalah yang paling umum. Mereka mewakili praktik terbaik yang dikembangkan dalam waktu yang lama oleh pengembang dalam proses pengujian aplikasi dan memperbaiki bug.

Pada artikel ini kita akan berbicara tentang pola struktural. Mereka dirancang untuk merancang aplikasi dengan mendefinisikan cara sederhana untuk berinteraksi dengan instance.

Yang paling umum adalah pola berikut:

  • Adaptor
  • Penghias
  • Tatapan
  • Adaptif (Ringan (Elemen), Terbang)
  • Proksi

Adaptor


Adaptor adalah templat yang memungkinkan Anda untuk menerjemahkan (mentransfer) antarmuka satu kelas ke kelas lainnya. Ini memungkinkan kelas untuk bekerja bersama, yang seringkali tidak mungkin karena struktur yang tidak kompatibel.

Bayangkan bahwa Targaryen memutuskan untuk bertarung dengan semua kekuatan yang mereka miliki (Flawless and Dragons), dan Daenerys mencari cara untuk berinteraksi dengan masing-masing dari mereka.

Kami membutuhkan yang berikut:

  • Kelas tidak ternoda
  • Kelas naga
  • Kelas adaptor untuk lulus metode pembakaran kelas Dragon ke metode umum membunuh

class Unsullied {
    constructor(name) {
        this.name = name
        this.kill = this.kill.bind(this)
    }

    kill() {
        console.log(`Unsullied ${this.name} kill`)
    }
}

class Dragon {
    constructor(name) {
        this.name = name
        this.burn = this.burn.bind(this)
    }

    burn() {
        console.log(`Dragon ${this.name} burn`)
    }
}

class DragonAdapter extends Dragon {
    kill() {
        this.burn()
    }
}

(() => {
    const Army = [
        new DragonAdapter('Rhaegal'),
        new Unsullied('Grey worm'),
        new DragonAdapter('Drogon')
    ]
    Army.forEach(soldier => soldier.kill())
})()

Kasus Penggunaan


  • Ketika komponen atau modul baru harus berfungsi dengan yang ada dalam aplikasi atau ketika bagian dari kode telah diperbaiki sebagai hasil dari refactoring, tetapi harus berinteraksi dengan bagian yang lama.
  • Ketika Anda menemukan perpustakaan untuk memecahkan masalah sekunder dan ingin mengintegrasikannya ke dalam aplikasi Anda.

Penghias


Dekorator adalah templat yang dirancang untuk secara dinamis menambah perilaku atau fungsionalitas ke kelas yang ada. Ini adalah alternatif untuk subklasifikasi.

Misalkan kita ingin memeriksa apakah minuman yang disajikan untuk Raja Joffrey beracun.

Kami membutuhkan yang berikut:

  • Kelas raja
  • Misalnya Raja Joffrey
  • Kelas minum
  • Minuman Beracun kelas
  • Fungsi isNotPoisoned untuk menyelamatkan nyawa raja

function isNotPoisoned(t, n, descriptor) {
    const original = descriptor.value
    if(typeof original === 'function') {
        descriptor.value = function(...args) {
            const [drink] = args
            if(drink.constructor.name === 'poisonedDrink') throw new Error('Someone wants to kill the king')
            return original.apply(this, args)
        }
    }
    return descriptor
}

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

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

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

    //     
    //      
    @isNotPoisoned
    drink(drink) {
        console.log(`The king drank ${drink}`)
    }
}

(() => {
    const joffrey = new King('Joffrey Baratheon')
    const normalDrink = new Drink('water')
    const poisonedDrink = new Drink('poisoned water')
    joffrey.drink(normalDrink)
    joffrey.drink(poisonedDrink)
})()

Dekorator Gunakan Kasing


  • Ketika kita ingin menambahkan fungsi ke sejumlah besar kelas atau metode dengan konteks yang berbeda
  • Ketika kami ingin meningkatkan kelas yang dibuat sebelumnya, tetapi tidak punya waktu untuk refactoring penuh

Tatapan


Fasad - templat yang banyak digunakan di perpustakaan. Ini digunakan untuk menyediakan antarmuka yang terpadu dan sederhana dan menyembunyikan kerumitan subsistem atau subkelasnya.

Bayangkan kita ingin mengendalikan pasukan dalam pertempuran dengan orang barbar. Contoh pasukan kita memiliki metode untuk memindahkan kavaleri, tentara, dan raksasa. Tetapi kami terpaksa menyebut metode ini secara terpisah, yang membutuhkan banyak waktu. Bagaimana kita bisa membuat manajemen tentara lebih mudah?

Kami membutuhkan yang berikut:

  • spesimen tentara
  • Kelas ArmyFacade

class Horse {
    constructor(name) {
        this.name = name
    }
    attack() {
        console.log(`Infantry ${this.name} attack`)
    }
}

class Soldier {
    constructor(name) {
        this.name = name
    }
    attack() {
        console.log(`Soldier ${this.name} attack`)
    }
}

class Giant {
    constructor(name) {
        this.name = name
    }
    attack() {
        console.log(`Giant ${this.name} attack`)
    }
}

class ArmyFacade {
    constructor() {
        //  ,           
        this.army = [];
        (new Array(10)).fill().forEach((_, i) => this.army.push(new Horse(i + 1)));
        (new Array(10)).fill().forEach((_, i) => this.army.push(new Soldier(i + 1)));
        (new Array(1)).fill().forEach((_, i) => this.army.push(new Giant(i + 1)));
        this.getByType = this.getByType.bind(this)
    }
    getByType(type, occurency) {
        return this.army.filter(el => {
            return el.constructor.name === type && occurency-- > 0
        })
    }
    attack(armyInfo = {}) {
        const keys = Object.keys(armyInfo)
        let subArmy = []
        keys.forEach(soldier => {
            switch(soldier) {
                case 'horse': {
                    subArmy = [...subArmy, ...this.getByType('Horse', armyInfo.horse)]
                    break;
                }
                    case 'soldier': {
                    subArmy = [...subArmy, ...this.getByType('Soldier', armyInfo.soldier)]
                    break;
                }
                    case 'giant': {
                    subArmy = [...subArmy, ...this.getByType('Giant', armyInfo.giant)]
                    break;
                }
            }
        })
        subArmy.forEach(soldier => soldier.attack())
    }
}

(() => {
    const army = new ArmyFacade()
    army.attack({
        horse: 3,
        soldier: 5,
        giant: 1
    })
})()

Gunakan kasing


  • Ketika kita ingin mengonversi banyak baris kode, mungkin berulang, menjadi satu fungsi sederhana.

Oportunis


Adaptive - template yang dirancang untuk transfer data yang efisien antara banyak objek kecil. Ini digunakan untuk meningkatkan kinerja dan menghemat memori.

Misalkan kita ingin mengendalikan pasukan pejalan kaki kulit putih. Pada saat yang sama, pejalan kaki kami dapat memiliki tiga negara:

  • hidup
  • mati
  • dibangkitkan

Kami membutuhkan yang berikut:

  • Kelas WhiteWalker
  • kelas WhiteWalkerFlyweight
  • klien untuk kebangkitan pejalan kaki putih

class WhiteWalker {
    constructor({
        sprite,
        someOtherBigInformation
    }) {
        this.sprite = sprite
        this.someOtherBigInformation = someOtherBigInformation
        this.state = 'alive'
        this.resurrect = this.resurrect.bind(this)
        this.kill = this.kill.bind(this)
        this.getState = this.getState.bind(this)
    }
    kill() {
        this.state = 'dead'
    }
    resurrect() {
        this.state = 'resurrected'
    }
    getState() {
        return this.state
    }
}

const whiteWalker = new WhiteWalker({
    sprite: Date.now()
})

class WhiteWalkerFlyweight {
    constructor(position, name) {
        this.position = position
        this.name = name
        this.whiteWalker = whiteWalker
    }
    getInfo() {
        console.log(`The White Walker ${this.name} whit sprite ${this.whiteWalker.sprite} is ${this.whiteWalker.state}`)
    }
    getFatherInstance() {
        return this.whiteWalker
    }
}

(() => {
    const myArmy = []
    for(let i = 0; i < 10; i++) {
        myArmy.push(new WhiteWalkerFlyweight({
            x: Math.floor(Math.random() * 200),
            y: Math.floor(Math.random() * 200),
        }, i + 1))
    }
    myArmy.forEach(soldier => soldier.getInfo())
    console.log('KILL ALL')
    const [onOffWhiteWalker] = myArmy
    onOffWhiteWalker.getFatherInstance().kill()
    myArmy.forEach(soldier => soldier.getInfo())
    console.log('RESURRECT ALL')
    onOffWhiteWalker.getFatherInstance().resurrect()
    myArmy.forEach(soldier => soldier.getInfo())
})()

Gunakan kasing


  • Ketika kita ingin menghindari membuat sejumlah besar objek
  • Ketika kita ingin membuat objek yang mengkonsumsi banyak memori
  • Ketika kita membutuhkan benda yang ciptaannya membutuhkan perhitungan yang rumit
  • Ketika kita memiliki persediaan sumber daya yang terbatas: daya komputasi, memori, ruang, dll.

Proksi


Templat proxy, seperti namanya, digunakan sebagai tambahan atau pengganti untuk objek lain untuk mengontrol akses ke objek ini.

Bayangkan bahwa Ratu Cersei mengeluarkan dekrit yang melarang perekrutan lebih dari 100 tentara tanpa seizinnya. Bagaimana kita menerapkan ini?

Kami membutuhkan yang berikut:

  • prajurit kelas
  • Kelas ArmyProxy untuk kontrol proses
  • contoh kelas Cercei untuk izin

class Soldier {
    constructor(name) {
        this.name = name
    }
    attack() {
        console.log(`Soldier ${this.name} attack`)
    }
}

class Queen {
    constructor(name) {
        this.name = name
    }
    getConsent(casualNumber) {
        return casualNumber %2 === 0
    }
}

class ArmyProxy {
    constructor() {
        this.army = []
        this.available = 0
        this.queen = new Queen('Cercei')
        this.getQueenConsent = this.getQueenConsent.bind(this)
    }

    getQueenConsent() {
        return this.queen.getConsent(Math.floor(Math.random() * 200))
    }

    enrollSoldier() {
        if(!this.available) {
            const consent = this.getQueenConsent()
            if(!consent) {
                console.error(`The queen ${this.queen.name} deny the consent`)
                return
            }
            this.available = 100
        }
        this.army.push(new Soldier(this.army.length))
        this.available--
    }
}

(() => {
    const myArmy = new ArmyProxy()
    for(let i = 0; i < 1000; i++) {
        myArmy.enrollSoldier()
    }
    console.log(`I have ${myArmy.army.length} soldiers`)
})()

Gunakan kasing


  • Ketika objek yang ingin kita gunakan jauh (bersarang dalam-dalam) dan menyimpan logika di proksi agar tidak mempengaruhi klien
  • Saat kami ingin memberikan hasil perkiraan sebagai antisipasi hasil nyata, perhitungannya membutuhkan banyak waktu
  • Ketika kita ingin mengontrol akses atau pembuatan objek tanpa mengganggu logikanya

Kode github .

Catatan trans: ini adalah video yang bagus tentang pola desain.

Terima kasih atas perhatian Anda.

All Articles