Saya memiliki game dasar menara 2D di C ++.
Setiap peta adalah kelas terpisah yang mewarisi dari GameState. Peta mendelegasikan logika dan menggambar kode untuk setiap objek dalam permainan dan menetapkan data seperti jalur peta. Dalam pseudo-code, bagian logika mungkin terlihat seperti ini:
update():
for each creep in creeps:
creep.update()
for each tower in towers:
tower.update()
for each missile in missiles:
missile.update()
Benda-benda (merayap, menara dan rudal) disimpan dalam vektor-of-pointer. Menara harus memiliki akses ke vektor-creep dan vektor-rudal untuk membuat rudal baru dan mengidentifikasi target.
Pertanyaannya adalah: di mana saya mendeklarasikan vektor? Haruskah mereka menjadi anggota kelas Peta, dan diteruskan sebagai argumen ke fungsi tower.update ()? Atau dinyatakan secara global? Atau ada solusi lain yang saya lewatkan sepenuhnya?
Ketika beberapa kelas perlu mengakses data yang sama, di mana seharusnya data dinyatakan?
sumber
Jawaban:
Saat Anda membutuhkan satu instance kelas di seluruh program Anda, kami menyebut kelas itu layanan . Ada beberapa metode standar untuk mengimplementasikan layanan dalam program:
Lajang . Sekitar 10-15 tahun yang lalu, lajang yang satu desain-pola besar untuk tahu tentang. Namun, saat ini mereka dipandang rendah. Mereka jauh lebih mudah untuk multi-utas, tetapi Anda harus membatasi penggunaannya pada satu utas pada satu waktu, yang tidak selalu seperti yang Anda inginkan. Melacak masa hidup sama sulitnya dengan variabel global.
Kelas singleton tipikal akan terlihat seperti ini:
Ketergantungan Injeksi (DI) . Ini hanya berarti melewatkan layanan sebagai parameter konstruktor. Suatu layanan harus sudah ada untuk lulus ke dalam kelas, jadi tidak ada cara bagi dua layanan untuk saling bergantung; dalam 98% kasus, inilah yang Anda inginkan (dan untuk 2% lainnya, Anda selalu dapat membuat
setWhatever()
metode dan meneruskan layanan nanti) . Karena itu, DI tidak memiliki masalah kopling yang sama dengan opsi lainnya. Ini dapat digunakan dengan multithreading, karena setiap utas hanya dapat memiliki contoh sendiri dari setiap layanan (dan hanya berbagi yang benar-benar perlu). Itu juga membuat kode unit dapat diuji, jika Anda peduli tentang itu.Masalah dengan injeksi ketergantungan adalah ia membutuhkan lebih banyak memori; sekarang setiap instance kelas membutuhkan referensi ke setiap layanan yang akan digunakan. Juga, itu akan mengganggu untuk digunakan ketika Anda memiliki terlalu banyak layanan; ada kerangka kerja yang mengurangi masalah ini dalam bahasa lain, tetapi karena kurangnya refleksi C ++, kerangka kerja DI di C ++ cenderung lebih berfungsi daripada hanya melakukannya secara manual.
Lihat halaman ini (dari dokumentasi untuk Ninject, kerangka kerja C # DI) untuk contoh lain.
Injeksi ketergantungan adalah solusi yang biasa untuk masalah ini, dan merupakan jawaban yang akan Anda lihat paling terunggulkan untuk pertanyaan seperti ini di StackOverflow.com. DI adalah jenis Pembalikan Kontrol (IoC).
Pencari Lokasi Layanan . Pada dasarnya, hanya kelas yang menyimpan instance dari setiap layanan. Anda dapat melakukannya menggunakan refleksi , atau Anda bisa menambahkan contoh baru ke dalamnya setiap kali Anda ingin membuat layanan baru. Anda masih memiliki masalah yang sama seperti sebelumnya - Bagaimana kelas mengakses locator ini? - yang dapat diselesaikan dengan salah satu cara di atas, tetapi sekarang Anda hanya perlu melakukannya untuk
ServiceLocator
kelas Anda , bukan untuk puluhan layanan. Metode ini juga dapat diuji unit, jika Anda peduli tentang hal semacam itu.Service Locators adalah bentuk lain dari Inversion of Control (IoC). Biasanya, kerangka kerja yang melakukan injeksi dependensi otomatis juga akan memiliki pelacak layanan.
XNA (kerangka kerja pemrograman game C # Microsoft) mencakup pelacak layanan; untuk mempelajari lebih lanjut, lihat jawaban ini .
Ngomong-ngomong, IMHO menara tidak harus tahu tentang merinding. Kecuali jika Anda berencana hanya mengulang daftar creep untuk setiap menara, Anda mungkin ingin menerapkan beberapa partisi ruang nontrivial ; dan logika semacam itu tidak termasuk dalam kelas menara.
sumber
Saya pribadi akan menggunakan polimorfisme di sini. Mengapa memiliki
missile
vektor,tower
vektor, dan vektorcreep
.. ketika mereka semua memanggil fungsi yang sama;update
? Mengapa tidak memiliki vektor pointer ke beberapa kelas dasarEntity
atauGameObject
?Saya menemukan cara yang baik untuk merancang adalah berpikir 'apakah ini masuk akal dalam hal kepemilikan'? Jelas sebuah menara memiliki cara untuk memperbarui dirinya sendiri, tetapi apakah peta memiliki semua objek di dalamnya? Jika Anda ingin global, apakah Anda mengatakan bahwa tidak ada yang memiliki menara dan merinding? Global biasanya merupakan solusi buruk - mempromosikan pola desain yang buruk, namun jauh lebih mudah untuk dikerjakan. Pertimbangkan menimbang 'apakah saya ingin menyelesaikan ini?' dan 'apakah saya menginginkan sesuatu yang dapat saya gunakan kembali'?
Salah satu cara mengatasi hal ini adalah beberapa bentuk sistem pengiriman pesan. Yang
tower
dapat mengirim pesan kemap
(yang memiliki akses ke, mungkin referensi ke pemiliknya?) Yang ditabrakcreep
, danmap
kemudian memberitahucreep
itu telah dipukul. Ini sangat bersih dan memisahkan data.Cara lain adalah dengan hanya mencari peta itu sendiri untuk apa yang diinginkannya. Namun, mungkin ada masalah dengan urutan pembaruan di sini.
sumber
Ini adalah kasus di mana pemrograman berorientasi objek ketat (OOP) rusak.
Menurut prinsip-prinsip OOP, Anda harus mengelompokkan data dengan perilaku terkait menggunakan kelas. Tetapi Anda memiliki perilaku (penargetan) yang membutuhkan data yang tidak terkait satu sama lain (menara dan merayap). Dalam situasi ini, banyak programmer akan mencoba mengaitkan perilaku dengan bagian dari data yang dibutuhkan (misalnya menara menangani penargetan, tetapi tidak tahu tentang creep), tetapi ada opsi lain: jangan kelompok perilaku dengan data.
Alih-alih menjadikan perilaku penargetan sebagai metode kelas menara, menjadikannya fungsi bebas yang menerima menara dan merayap sebagai argumen. Ini mungkin memerlukan membuat lebih banyak anggota yang tersisa di menara dan kelas merayap publik, dan itu tidak masalah. Menyembunyikan data berguna, tetapi itu sarana, bukan tujuan itu sendiri, dan Anda tidak harus menjadi budaknya. Selain itu, anggota pribadi bukan satu-satunya cara untuk mengontrol akses ke data - jika data tidak dialihkan ke fungsi dan bukan global, itu secara efektif disembunyikan dari fungsi itu. Jika menggunakan teknik ini memungkinkan Anda menghindari data global, Anda mungkin sebenarnya meningkatkan enkapsulasi.
Contoh ekstrem dari pendekatan ini adalah arsitektur sistem entitas .
sumber