9 alternatif untuk tim yang buruk (pola desain)

gambar

Apa ini dan mengapa?


Saat mendesain, pengembang mungkin menghadapi masalah: makhluk dan benda mungkin memiliki kemampuan berbeda dalam kombinasi berbeda. Katak melompat dan berenang, bebek berenang dan terbang, tetapi tidak dengan berat, dan katak bisa terbang dengan dahan dan bebek. Karenanya, lebih mudah untuk beralih dari warisan ke komposisi dan menambah kemampuan secara dinamis. Kebutuhan untuk menghidupkan katak terbang menyebabkan penolakan yang tidak dibenarkan atas metode kemampuan dan penghapusan kode mereka dalam tim di salah satu implementasi. Ini dia:

class CastSpellCommand extends Command { constructor (source, target, spell) { this.source = source; this.target = target; this.spell = spell; } execute () { const spellAbility = this.source.getAbility(SpellCastAbility); //      if (!cond) return error if (spellAbility == null) { throw new Error('NoSpellCastAbility'); } this.addChildren(new PayManaCommand(this.source, this.spell.manaCost)); this.addChildren(this.spell.getCommands(this.source, this.target)); //   : healthAbility.health = Math.max( 0, resultHealth ); } } // : async onMeleeHit (meleeHitCommand) { await view.drawMeleeHit( meleeHitCommand.source, meleeHitCommand.target ); } async onDealDamage (dealDamageCommand) { await view.showDamageNumbers( dealDamageCommand.target, dealDamageCommand.damage ); } 

Apa yang bisa dilakukan?


Pertimbangkan beberapa pendekatan yang berbeda sifatnya:

Pengamat


 class Executor extends Observer {/* ... */} class Animator extends Observer {/* ... */} 

Solusi klasik yang terkenal bagi para programmer. Anda hanya perlu mengubahnya minimal untuk memeriksa nilai yang dikembalikan oleh pengamat:

 this.listeners.reduce((result, listener) => result && listener(action), true) 

Kerugian: pengamat harus berlangganan ke acara dalam urutan yang benar.

Jika Anda melakukan penanganan kesalahan, animator juga akan dapat menampilkan animasi dari tindakan yang gagal. Anda dapat memberikan nilai sebelumnya kepada pengamat, secara konseptual, solusinya tetap sama. Apakah metode pengamat atau fungsi panggilan balik dipanggil, apakah loop biasa digunakan dan bukan konvolusi, detailnya tidak begitu signifikan.

Tinggalkan apa adanya


Dan memang. Pendekatan saat ini memiliki kekurangan dan kelebihan:

  1. Menguji kemampuan untuk menjalankan perintah membutuhkan eksekusi perintah
  2. Argumen dalam urutan yang berubah, kondisi, awalan metode terprogram
  3. Loop dependensi (perintah <mantra <perintah)
  4. Entitas tambahan untuk setiap tindakan (metode digantikan oleh metode, kelas dan konstruktornya)
  5. Pengetahuan dan tindakan berlebihan dari masing-masing tim: dari mekanika game hingga kesalahan sinkronisasi dan manipulasi langsung properti orang lain
  6. Antarmuka menyesatkan (mengeksekusi tidak hanya panggilan, tetapi juga menambahkan perintah melalui addChildren; yang, jelas melakukan sebaliknya)
  7. Kebutuhan yang meragukan dan implementasi instruksi rekursif per se
  8. Kelas dispatcher, jika ada, tidak menjalankan fungsinya
  9. [+] Diduga satu-satunya cara untuk menghidupkan dalam praktik, jika animasinya memerlukan data lengkap (ditunjukkan sebagai alasan utama)
  10. [+] Mungkin alasan lain

Beberapa kekurangan dapat ditangani secara terpisah, tetapi sisanya membutuhkan perubahan yang lebih drastis.

ad hoc


  • Kondisi untuk pelaksanaan tim, terutama mekanisme permainan, harus dikeluarkan dari tim dan dieksekusi secara terpisah. Kondisi dapat berubah dalam runtime, dan menyoroti tombol tidak aktif dalam warna abu-abu terjadi dalam praktik jauh sebelum bekerja pada animasi dimulai, belum lagi logika. Untuk menghindari penyalinan, mungkin masuk akal untuk menyimpan kondisi umum dalam prototipe kemampuan.
  • Metode pengembalian, dikombinasikan dengan paragraf sebelumnya, kebutuhan untuk pemeriksaan tersebut akan hilang:

     const spellAbility = this.source.getAbility(SpellCastAbility); //      if (!cond) return error if (spellAbility == null) { throw new Error('NoSpellCastAbility'); } 

    Mesin javascript itu sendiri akan menampilkan TypeError yang benar ketika metode ini keliru disebut.
  • Tim juga tidak membutuhkan pengetahuan seperti itu:

     healthAbility.health = Math.max( 0, resultHealth ); 
  • Untuk memecahkan masalah argumen yang mengubah tempat, mereka bisa dilewatkan oleh objek.
  • Meskipun kode panggilan tidak tersedia untuk dipelajari, tampaknya sebagian besar kekurangan tumbuh karena cara yang tidak optimal dalam memohon tindakan game. Misalnya, penangan tombol mengakses entitas tertentu. Oleh karena itu, menggantinya di handler dengan perintah khusus tampaknya cukup alami. Jika Anda memiliki operator, jauh lebih mudah untuk memanggil animasi setelah tindakan, Anda dapat mentransfer informasi yang sama dengannya, sehingga tidak akan kekurangan data.

Antrian


Untuk menampilkan animasi tindakan setelah tindakan selesai, cukup menambahkannya ke antrian dan menjalankannya kira-kira seperti pada solusi 1.

 [ [ walkRequirements, walkAction, walkAnimation ], [ castRequirements, castAction, castAnimation ], // ... ] 

Entah entitas mana yang ada dalam array: fungsi dilarang dengan parameter yang diperlukan, instance kelas khusus, atau objek biasa.
Nilai dari solusi tersebut adalah kesederhanaan dan transparansi, mudah untuk membuat jendela geser untuk melihat N perintah terakhir.

Sangat cocok untuk prototyping dan debugging.

Kelas pengganti


Kami membuat kelas animasi untuk kemampuan.

 class MovementAbility { walk (...args) { // action } } class AnimatedMovementAbility { walk (...args) { // animation } } 

Jika tidak mungkin untuk membuat perubahan pada kelas panggilan, kami mewarisi darinya atau menghias metode yang diinginkan sehingga memanggil animasi. Atau kami mengirimkan animasi alih-alih kemampuan, mereka memiliki antarmuka yang sama.

Sangat cocok ketika Anda membutuhkan serangkaian metode yang hampir sama, mereka dapat secara otomatis diperiksa dan diuji.

Kombinasi Metode


 const AnimatedMovementAbility = combinedClass(MovementAbility, { ['*:before'] (method, ...args) { // call requirements }, ['*:after'] (method, ...args) { // call animations } }) 

Ini akan menjadi peluang yang menarik dengan dukungan bahasa ibu.
Baik untuk menggunakannya jika opsi ini lebih produktif, walaupun proxy sebenarnya dibutuhkan.

Proksi


Kami membungkus kemampuan dalam proxy, menangkap metode dalam getter.

 new Proxy(new MovementAbility, {/* handler */}) 

Kerugian: berkali-kali lebih lambat dari panggilan biasa, yang tidak begitu penting untuk animasi. Pada server yang memproses jutaan objek, perlambatan akan terlihat, tetapi server tidak perlu animasi.

Janji


Anda dapat membuat rantai dari Janji, tetapi ada opsi lain (ES2018):

 for await (const action of actionDispatcher.getActions()) { // } 

getActions mengembalikan iterator tindakan asinkron. Metode iterator selanjutnya mengembalikan Janji yang Ditangguhkan dari tindakan selanjutnya. Setelah memproses acara dari pengguna dan server, kami memanggil resolusinya (), buat janji baru.

Tim yang lebih baik


Buat objek seperti ini:

 {actor, ability, method, options} 

Kode diturunkan untuk memeriksa dan memanggil metode kemampuan dengan parameter. Opsi termudah dan paling produktif.

Catatan


Source: https://habr.com/ru/post/id439506/


All Articles