Tentu itu tergantung situasi. Tetapi ketika objek atau sistem tuas yang lebih rendah berkomunikasi dengan sistem tingkat yang lebih tinggi, haruskah panggilan balik atau peristiwa lebih disukai daripada mempertahankan pointer ke objek tingkat yang lebih tinggi?
Sebagai contoh, kami memiliki world
kelas yang memiliki variabel anggota vector<monster> monsters
. Ketika monster
kelas berkomunikasi dengan world
, haruskah saya lebih suka menggunakan fungsi panggilan balik atau haruskah saya memiliki pointer ke world
kelas di dalam monster
kelas?
architecture
software-engineering
hidayat
sumber
sumber
Jawaban:
Ada tiga cara utama agar satu kelas dapat berbicara dengan yang lain tanpa terikat erat dengannya:
Ketiganya terkait erat satu sama lain. Sistem acara dalam banyak hal hanyalah daftar panggilan balik. Panggilan balik lebih atau kurang antarmuka dengan metode tunggal.
Di C ++, saya jarang menggunakan callback:
C ++ tidak memiliki dukungan yang baik untuk panggilan balik yang mempertahankan
this
penunjuk mereka , jadi sulit untuk menggunakan panggilan balik dalam kode berorientasi objek.Callback pada dasarnya adalah antarmuka satu metode yang tidak dapat diperluas. Seiring waktu, saya menemukan bahwa saya hampir selalu akhirnya membutuhkan lebih dari satu metode untuk mendefinisikan antarmuka itu dan panggilan balik tunggal jarang cukup.
Dalam hal ini, saya mungkin akan melakukan antarmuka. Dalam pertanyaan Anda, Anda sebenarnya tidak menguraikan apa yang
monster
sebenarnya perlu dikomunikasikanworld
. Mengambil tebakan, saya akan melakukan sesuatu seperti:Idenya di sini adalah bahwa Anda hanya memasukkan
IWorld
(yang merupakan nama jelek) minimum yangMonster
perlu diakses. Pandangannya tentang dunia harus sesempit mungkin.sumber
Jangan gunakan fungsi panggilan balik untuk menyembunyikan fungsi apa yang Anda panggil. Jika Anda dimasukkan ke dalam fungsi panggilan balik, dan fungsi panggilan balik itu akan memiliki satu dan hanya satu fungsi yang ditetapkan untuk itu, maka Anda tidak benar-benar merusak kopling sama sekali. Anda hanya menutupinya dengan lapisan abstraksi lain. Anda tidak mendapatkan apa pun (selain dari waktu kompilasi), tetapi Anda kehilangan kejelasan.
Saya tidak akan menyebutnya sebagai praktik terbaik, tetapi merupakan pola umum untuk memiliki entitas yang berisi sesuatu untuk memiliki pointer ke orang tua mereka.
Yang sedang berkata, mungkin bernilai saat Anda menggunakan pola antarmuka untuk memberikan monster Anda sejumlah fungsi yang dapat mereka panggil di dunia.
sumber
Secara umum saya mencoba dan menghindari tautan dua arah, tetapi jika saya harus memilikinya, saya benar-benar yakin bahwa ada satu metode untuk membuatnya dan satu untuk memutusnya, sehingga Anda tidak pernah mendapatkan inkonsistensi.
Seringkali Anda dapat menghindari tautan dua arah sepenuhnya dengan mengirimkan data yang diperlukan. Refactoring yang sepele adalah membuatnya agar alih-alih memiliki monster yang menjaga tautan ke dunia, Anda meneruskan dunia dengan merujuk pada metode monster yang membutuhkannya. Lebih baik lagi adalah dengan hanya memberikan antarmuka untuk bit-bit dunia yang benar-benar dibutuhkan monster, artinya monster itu tidak bergantung pada implementasi konkret dunia. Ini sesuai dengan Prinsip Segregasi Antarmuka dan Prinsip Pembalikan Ketergantungan , tetapi tidak mulai memperkenalkan abstraksi berlebih yang kadang-kadang Anda dapatkan dengan peristiwa, sinyal + slot, dll.
Di satu sisi, Anda bisa berpendapat bahwa menggunakan panggilan balik adalah antarmuka mini yang sangat khusus, dan itu bagus. Anda harus memutuskan apakah Anda dapat mencapai tujuan dengan lebih bermakna melalui satu kumpulan metode dalam objek antarmuka atau beberapa yang berbeda dalam panggilan balik yang berbeda.
sumber
Saya mencoba untuk menghindari benda-benda berisi memanggil wadah mereka karena saya menemukan hal itu menyebabkan kebingungan, itu menjadi terlalu mudah untuk dibenarkan, itu akan menjadi terlalu sering digunakan dan itu menciptakan ketergantungan yang tidak dapat dikelola.
Menurut pendapat saya, solusi yang ideal adalah kelas yang lebih tinggi cukup pintar untuk mengelola kelas yang lebih rendah. Sebagai contoh, dunia yang mengetahui untuk menentukan apakah tabrakan antara monster dan ksatria terjadi tanpa mengetahui tentang yang lain lebih baik bagiku daripada monster yang bertanya kepada dunia apakah itu bertabrakan dengan seorang ksatria.
Opsi lain dalam kasus Anda, saya mungkin akan mencari tahu mengapa kelas monster perlu tahu tentang kelas dunia dan Anda kemungkinan besar akan menemukan bahwa ada sesuatu di kelas dunia yang dapat dibagi menjadi kelas tersendiri yang dibuatnya. merasakan kelas monster untuk tahu tentang.
sumber
Anda tidak akan pergi jauh tanpa peristiwa, jelas, tetapi bahkan sebelum mulai menulis (dan mendesain) sistem acara, Anda harus mengajukan pertanyaan sesungguhnya: mengapa monster itu berkomunikasi dengan kelas dunia? Haruskah itu benar?
Mari kita ambil situasi "klasik", monster menyerang pemain.
Monster sedang menyerang: dunia bisa mengidentifikasi situasi di mana seorang pahlawan berada di sebelah monster, dan memberitahu monster itu untuk menyerang. Jadi fungsi dalam monster adalah:
Tetapi dunia (yang sudah tahu monster itu) tidak perlu diketahui oleh monster itu. Sebenarnya, monster itu bisa mengabaikan keberadaan kelas Dunia, yang mungkin lebih baik.
Hal yang sama ketika monster bergerak (saya membiarkan sub-sistem mendapatkan makhluk dan menangani perhitungan bergerak / niat untuk itu, monster itu hanya sekumpulan data, tetapi banyak orang akan mengatakan ini bukan OOP asli).
Maksud saya adalah: peristiwa (atau panggilan balik) bagus, tentu saja, tetapi itu bukan satu-satunya jawaban untuk setiap masalah yang akan Anda hadapi.
sumber
Kapan pun saya bisa, saya mencoba membatasi komunikasi antara objek ke model permintaan dan respons. Ada urutan parsial tersirat pada objek dalam program saya sehingga antara dua objek A dan B, mungkin ada cara bagi A untuk secara langsung atau tidak langsung memanggil metode B atau untuk B untuk secara langsung atau tidak langsung memanggil metode A , tetapi tidak pernah mungkin bagi A dan B untuk saling memanggil metode satu sama lain. Terkadang, tentu saja, Anda ingin memiliki komunikasi mundur dengan penelepon suatu metode. Ada beberapa cara yang saya suka lakukan ini, dan tidak satu pun dari mereka adalah panggilan balik.
Salah satu caranya adalah dengan memasukkan lebih banyak informasi dalam nilai balik dari pemanggilan metode, yang berarti kode klien dapat memutuskan apa yang harus dilakukan dengannya setelah prosedur mengembalikan kontrol ke sana.
Cara lain adalah dengan memanggil objek anak bersama. Yaitu, jika A memanggil metode pada B, dan B perlu mengkomunikasikan beberapa informasi ke A, B memanggil metode pada C, di mana A dan B keduanya dapat memanggil C, tetapi C tidak dapat memanggil A atau B. Objek A akan menjadi bertanggung jawab untuk mendapatkan informasi dari C setelah B mengembalikan kontrol ke A. Perhatikan bahwa ini sebenarnya tidak jauh berbeda dari cara pertama yang saya usulkan. Objek A masih dapat hanya mengambil informasi dari nilai kembali; tidak ada metode objek A yang dipanggil oleh B atau C. Variasi trik ini adalah untuk lulus C sebagai parameter untuk metode, tetapi pembatasan pada hubungan C ke A dan B masih berlaku.
Sekarang, pertanyaan penting adalah mengapa saya bersikeras melakukan hal-hal seperti ini. Ada tiga alasan utama:
self
sementara metode mengeksekusi adalah satu metode dan tidak ada yang lain. Ini adalah jenis pemikiran yang sama yang mungkin menyebabkan seseorang untuk menempatkan mutex pada objek bersamaan.Saya tidak menentang semua penggunaan callback. Sesuai dengan kebijakan saya tentang tidak pernah "memanggil penelepon," jika suatu objek A memanggil metode pada B dan meneruskan panggilan balik ke sana, panggilan balik tidak dapat mengubah keadaan internal A, dan itu termasuk objek yang dienkapsulasi oleh A dan objek dalam konteks A. Dengan kata lain, callback hanya dapat memanggil metode pada objek yang diberikan kepadanya oleh B. Callback, pada dasarnya, berada di bawah batasan yang sama dengan B.
Salah satu jalan keluar terakhir untuk mengikat adalah bahwa saya akan mengijinkan doa dari setiap fungsi murni, terlepas dari pemesanan parsial ini yang telah saya bicarakan. Fungsi murni sedikit berbeda dari metode yang tidak dapat diubah atau bergantung pada efek atau efek samping yang dapat berubah, sehingga tidak ada kekhawatiran tentang hal-hal yang membingungkan.
sumber
Sendiri? Saya hanya menggunakan singleton.
Ya, oke, desain buruk, tidak berorientasi objek, dll. Anda tahu? Saya tidak peduli . Saya sedang menulis game, bukan showcase teknologi. Tidak ada yang akan memberi saya kode. Tujuannya adalah membuat game yang menyenangkan, dan semua yang menghalangi saya akan menghasilkan game yang kurang menyenangkan.
Apakah Anda pernah menjalankan dua dunia sekaligus? Mungkin! Mungkin kamu akan. Tetapi kecuali jika Anda dapat memikirkan situasi itu sekarang, Anda mungkin tidak akan.
Jadi, solusi saya: bikin World singleton. Panggil fungsi di atasnya. Selesaikan dengan seluruh kekacauan. Anda bisa memberikan parameter tambahan untuk setiap fungsi tunggal - dan jangan salah, di situlah ini mengarah. Atau Anda bisa menulis kode yang berfungsi.
Melakukannya dengan cara ini membutuhkan sedikit disiplin untuk membersihkan segala sesuatunya ketika menjadi berantakan (itu "ketika", bukan "jika") tetapi tidak ada cara Anda dapat mencegah kode menjadi berantakan - baik Anda memiliki masalah spageti, atau ribuan -Pemain-of-abstraksi. Setidaknya dengan cara ini Anda tidak sedang menulis kode yang tidak perlu dalam jumlah besar.
Dan jika Anda memutuskan untuk tidak lagi menginginkan singleton, biasanya cukup mudah untuk menyingkirkannya. Membutuhkan beberapa pekerjaan, membutuhkan melewati satu miliar parameter di sekitar, tetapi itu adalah parameter yang Anda harus melewati sekitar
sumber