Bagaimana saya bisa memiliki objek berinteraksi dan berkomunikasi satu sama lain tanpa memaksakan hierarki?

9

Saya harap ocehan ini akan memperjelas pertanyaan saya - saya benar-benar mengerti jika mereka tidak mau, jadi beri tahu saya jika itu masalahnya, dan saya akan mencoba membuat diri saya lebih jelas.

Meet BoxPong , game yang sangat sederhana yang saya buat untuk berkenalan dengan pengembangan game berorientasi objek. Seret kotak untuk mengontrol bola dan mengumpulkan benda-benda kuning.
Membuat BoxPong membantu saya merumuskan, antara lain, pertanyaan mendasar: bagaimana saya bisa memiliki objek yang berinteraksi satu sama lain tanpa harus "saling memiliki"? Dengan kata lain, adakah cara agar objek tidak hierarkis, melainkan hidup berdampingan? (Saya akan pergi ke detail lebih lanjut di bawah ini.)

Saya menduga masalah objek yang hidup berdampingan adalah masalah umum, jadi saya berharap ada cara yang mapan untuk menyelesaikannya. Saya tidak ingin menemukan kembali roda persegi, jadi saya kira jawaban ideal yang saya cari adalah "inilah pola desain yang biasa digunakan untuk menyelesaikan masalah Anda."

Terutama di gim-gim sederhana seperti BoxPong, jelas ada, atau seharusnya ada, beberapa benda yang hidup berdampingan di tingkat yang sama. Ada sebuah kotak, ada sebuah bola, ada sebuah koleksi. Yang bisa saya ungkapkan dalam bahasa berorientasi objek, meskipun - atau begitulah tampaknya - adalah hubungan HAS-A yang ketat . Itu dilakukan melalui variabel anggota. Saya tidak bisa begitu saja memulai balldan membiarkannya melakukan hal itu, saya membutuhkannya secara permanen menjadi milik objek lain. Saya telah mengaturnya sehingga objek permainan utama memiliki kotak, dan kotak itu pada gilirannya memiliki bola, dan memiliki penghitung skor. Setiap objek juga memilikiupdate()metode, yang menghitung posisi, arah dll, dan saya pergi ke cara yang sama di sana: Saya memanggil metode pembaruan objek permainan utama, yang memanggil metode pembaruan semua anak-anaknya, dan mereka pada gilirannya memanggil metode pembaruan semua anak - anak mereka . Ini adalah satu-satunya cara saya bisa melihat untuk membuat game berorientasi objek, tetapi saya merasa itu bukan cara yang ideal. Lagipula, saya tidak akan menganggap bola sebagai milik kotak, melainkan berada di level yang sama dan berinteraksi dengannya. Saya kira itu bisa dicapai dengan mengubah semua objek game menjadi variabel anggota dari objek game utama, tapi saya tidak melihat itu menyelesaikan apa pun. Maksud saya ... mengesampingkan kekacauan yang jelas, bagaimana mungkin ada cara bagi bola dan kotak untuk saling mengenal , yaitu berinteraksi?

Ada juga masalah objek yang perlu saling menyampaikan informasi. Saya memiliki sedikit pengalaman menulis kode untuk SNES, di mana Anda memiliki akses ke hampir seluruh RAM sepanjang waktu. Katakanlah Anda membuat musuh khusus untuk Super Mario World , dan Anda ingin itu menghapus semua koin Mario, lalu simpan saja nol untuk mengatasi $ 0DBF, tidak masalah. Tidak ada batasan yang mengatakan musuh tidak dapat mengakses status pemain. Saya kira saya telah dimanjakan oleh kebebasan ini, karena dengan C ++ dan sejenisnya saya sering bertanya-tanya bagaimana membuat nilai dapat diakses oleh beberapa objek lain (atau bahkan global).
Menggunakan contoh BoxPong, bagaimana jika saya ingin bola memantul dari tepi layar? widthdan heightsifat-sifat Gamekelas,balluntuk memiliki akses ke mereka. Saya bisa meneruskan nilai-nilai seperti ini (baik melalui konstruktor atau metode di mana mereka diperlukan), tetapi itu hanya menjerit praktik buruk kepada saya.

Saya kira masalah utama saya adalah bahwa saya perlu objek untuk saling mengenal, tetapi satu-satunya cara saya bisa melihatnya adalah hierarki yang ketat, yang jelek dan tidak praktis.

Saya pernah mendengar tentang "kelas teman" di C ++ dan agak tahu bagaimana mereka bekerja, tetapi jika mereka adalah solusi akhir semua, maka kenapa saya tidak melihat friendkata kunci dituangkan di setiap proyek C ++, dan kenapa konsep tidak ada di setiap bahasa OOP? (Hal yang sama berlaku untuk pointer fungsi, yang baru saja saya pelajari.)

Terima kasih sebelumnya atas jawaban dalam bentuk apa pun - dan sekali lagi, jika ada bagian yang tidak masuk akal bagi Anda, beri tahu saya.

ay
sumber
2
Sebagian besar industri game telah bergerak ke arah arsitektur Entity-Component-System, dan variasinya. Ini adalah pola pikir yang berbeda dari pendekatan OO tradisional tetapi bekerja dengan baik dan masuk akal begitu konsep itu meresap. Unity menggunakannya. Sebenarnya, Unity hanya menggunakan bagian Entity-Component tetapi didasarkan pada ECS.
Dunk
Masalah memungkinkan kelas untuk berkolaborasi satu sama lain tanpa sepengetahuan satu sama lain diselesaikan oleh pola desain Mediator. Sudahkah Anda melihatnya?
Fuhrmanator

Jawaban:

13

Secara umum, ternyata sangat buruk jika objek dari tingkat yang sama tahu tentang satu sama lain. Begitu benda saling mengenal, mereka diikat, atau digabungkan satu sama lain. Ini membuat mereka sulit untuk berubah, sulit untuk diuji, sulit untuk dipertahankan.

Ini bekerja jauh lebih baik jika ada beberapa objek "di atas" yang tahu tentang keduanya dan dapat mengatur interaksi di antara mereka. Objek yang tahu tentang dua rekan dapat mengikat mereka bersama melalui injeksi ketergantungan atau melalui peristiwa atau lewat pesan (atau mekanisme decoupling lainnya). Ya, itu mengarah ke sedikit hirarki buatan, tapi itu jauh lebih baik daripada kekacauan spageti yang Anda dapatkan ketika hal-hal hanya berinteraksi mau tak mau. Itu hanya lebih penting dalam C ++, karena Anda memerlukan sesuatu untuk memiliki masa pakai objek juga.

Jadi singkatnya, Anda bisa melakukannya hanya dengan memiliki objek berdampingan di mana saja diikat bersama oleh akses ad hoc, tapi itu ide yang buruk. Hirarki menyediakan ketertiban dan kepemilikan yang jelas. Hal utama yang perlu diingat adalah bahwa objek dalam kode tidak harus objek dalam kehidupan nyata (atau bahkan game). Jika objek dalam game tidak membuat hierarki yang baik, abstraksi yang berbeda mungkin lebih baik.

Telastyn
sumber
2

Menggunakan contoh BoxPong, bagaimana jika saya ingin bola memantul dari tepi layar? lebar dan tinggi adalah properti dari kelas Game, dan saya membutuhkan bola untuk memiliki akses ke mereka.

Tidak!

Saya pikir masalah utama yang Anda miliki, adalah Anda menggunakan "Pemrograman Berorientasi Objek" sedikit terlalu harfiah. Dalam OOP, suatu objek tidak mewakili "benda" tetapi "ide" yang berarti bahwa "Bola", "Game", "Fisika", "Matematika", "Tanggal", dll. Semua adalah objek yang valid. Juga tidak ada persyaratan bagi objek untuk "tahu" tentang apa pun. Misalnya, Date.Now().getTommorrow()Akan bertanya pada komputer hari apa hari ini, menerapkan aturan tanggal misterius untuk mengetahui tanggal besok, dan mengembalikannya ke pemanggil. The Dateobjek tidak tahu tentang apa-apa lagi, hanya perlu meminta informasi yang diperlukan dari sistem. Juga, Math.SquareRoot(number)tidak perlu tahu apa pun selain logika cara menghitung akar kuadrat.

Jadi, dalam contoh Anda yang saya kutip, "Bola" seharusnya tidak tahu apa-apa tentang "Kotak". Box dan Balls adalah ide yang sangat berbeda, dan tidak memiliki hak untuk berbicara satu sama lain. Tetapi mesin Fisika memang tahu apa itu Box and Ball (atau setidaknya, ThreeDShape), dan ia tahu di mana mereka berada, dan apa yang seharusnya terjadi pada mereka. Jadi jika bola menyusut karena dingin, Mesin Fisika akan memberi tahu bola itu bahwa ia lebih kecil sekarang.

Ini seperti membangun mobil. Sebuah chip komputer tidak tahu apa-apa tentang mesin mobil, tetapi mobil dapat menggunakan chip komputer untuk mengendalikan mesin. Gagasan sederhana untuk menggunakan hal-hal kecil dan sederhana bersama-sama untuk menciptakan hal yang sedikit lebih besar, lebih kompleks, yang dengan sendirinya dapat digunakan kembali sebagai komponen dari bagian yang lebih kompleks lainnya.

Dan dalam contoh Mario Anda, bagaimana jika Anda berada di ruang tantangan di mana menyentuh musuh tidak menguras koin Marios, tetapi hanya mengeluarkannya dari ruangan itu? Adalah di luar ruang ide Mario atau musuh bahwa Mario harus kehilangan koin ketika menyentuh musuh (pada kenyataannya, jika Mario memiliki bintang kebal, Dia malah membunuh musuh). Jadi objek apa pun (domain / ide) yang bertanggung jawab atas apa yang terjadi ketika mario menyentuh musuh adalah satu-satunya yang perlu diketahui tentang keduanya, dan harus melakukan apa pun untuk salah satu dari mereka (sejauh objek itu memungkinkan perubahan yang didorong oleh eksternal) ).

Juga, dengan pernyataan Anda tentang objek yang memanggil anak-anak Update(), itu sangat rawan bug seperti apa jika Updatedisebut beberapa kali per frame dari orang tua yang berbeda? (bahkan jika Anda menangkap ini, itu adalah waktu CPU yang terbuang yang dapat memperlambat permainan Anda) Semua orang hanya boleh menyentuh apa yang mereka butuhkan, ketika mereka perlu. Jika Anda menggunakan Pembaruan (), Anda harus menggunakan beberapa bentuk pola berlangganan untuk memastikan semua Pembaruan dipanggil satu kali per bingkai (jika ini tidak ditangani untuk Anda seperti di Unity)

Mempelajari cara mendefinisikan ide domain Anda menjadi blok yang jelas, terisolasi, terdefinisi dengan baik, dan mudah digunakan akan menjadi faktor terbesar dalam seberapa baik Anda dapat memanfaatkan OOP.

Tezra
sumber
1

Meet BoxPong, game yang sangat sederhana yang saya buat untuk berkenalan dengan pengembangan game berorientasi objek.

Membuat BoxPong membantu saya merumuskan, antara lain, pertanyaan mendasar: bagaimana saya bisa memiliki objek yang berinteraksi satu sama lain tanpa harus "saling memiliki"?

Saya memiliki sedikit pengalaman menulis kode untuk SNES, di mana Anda memiliki akses ke hampir seluruh RAM sepanjang waktu. Katakanlah Anda membuat musuh khusus untuk Super Mario World, dan Anda ingin itu menghapus semua koin Mario, lalu simpan saja nol untuk mengatasi $ 0DBF, tidak masalah.

Anda tampaknya kehilangan titik pemrograman berorientasi objek.

Pemrograman Berorientasi Objek adalah tentang mengelola dependensi dengan secara selektif membalik dependensi kunci tertentu dalam arsitektur Anda sehingga Anda dapat mencegah kekakuan, kerapuhan, dan tidak dapat digunakan kembali.

Apa itu ketergantungan? Ketergantungan adalah ketergantungan pada sesuatu yang lain. Ketika Anda menyimpan nol untuk mengatasi $ 0DBF, Anda mengandalkan fakta bahwa alamat itu adalah tempat koin Mario berada dan bahwa koin tersebut direpresentasikan sebagai bilangan bulat. Kode Musuh Kustom Anda bergantung pada kode yang menerapkan Mario dan koinnya. Jika Anda membuat perubahan ke tempat Mario menyimpan koinnya di memori, Anda harus secara manual memperbarui semua kode yang merujuk pada lokasi memori.

Kode berorientasi objek adalah semua tentang membuat kode Anda bergantung pada abstraksi dan bukan pada detail. Jadi, bukannya

class Mario
{
    public:
        int coins;
}

kamu akan menulis

class Mario
{
    public:
        void LoseCoins();

    private:
        int coins;
}

Sekarang, jika Anda ingin mengubah cara Mario menyimpan koinnya dari int ke panjang atau ganda atau menyimpannya di jaringan atau menyimpannya di database atau memulai proses panjang lainnya, Anda membuat perubahan di satu tempat: the Kelas Mario, dan semua kode Anda yang lain terus bekerja tanpa perubahan.

Karena itu, ketika Anda bertanya

bagaimana saya bisa memiliki objek yang berinteraksi satu sama lain tanpa harus saling "milik"?

Anda benar-benar bertanya:

bagaimana saya dapat memiliki kode yang secara langsung bergantung satu sama lain tanpa abstraksi?

yang bukan pemrograman berorientasi objek.

Saya sarankan Anda mulai dengan membaca semuanya di sini: http://objectmentor.com/omSolutions/oops_what.html dan kemudian cari semuanya di youtube oleh Robert Martin dan saksikan semuanya.

Jawaban saya berasal darinya, dan beberapa di antaranya langsung dikutip darinya.

Mark Murfin
sumber
Terima kasih atas jawabannya (dan halaman yang Anda tautkan; terlihat menarik). Saya benar-benar tahu tentang abstraksi dan penggunaan kembali, tetapi saya kira saya tidak memberikan jawaban yang baik. Namun, dari contoh kode yang Anda berikan, saya bisa lebih baik menggambarkan poin saya sekarang! Anda pada dasarnya mengatakan bahwa objek musuh tidak boleh dilakukan mario.coins = 0;, tetapi mario.loseCoins();, yang baik dan benar - tetapi poin saya adalah, bagaimana mungkin musuh memiliki akses ke marioobjek itu? mariomenjadi variabel anggota enemytampaknya tidak benar bagi saya.
vvye
Yah jawaban sederhana adalah untuk melewati Mario sebagai argumen untuk fungsi di Musuh. Anda mungkin memiliki fungsi seperti marioNearby () atau attackMario () yang akan menjadikan Mario sebagai argumen. Jadi, kapan pun logika di balik kapan Musuh dan Mario berinteraksi, itu akan dipanggil, Anda akan memanggil musuh.marioNearby (mario) yang akan memanggil mario.loseCoins (); Kemudian di jalan Anda dapat memutuskan bahwa ada kelas musuh yang menyebabkan mario hanya kehilangan satu koin atau bahkan mendapatkan koin. Anda sekarang memiliki satu tempat untuk melakukan perubahan yang tidak menyebabkan perubahan efek samping ke kode lain.
Mark Murfin
Dengan memberikan Mario kepada musuh, Anda baru saja menyambungkannya. Mario dan Musuh seharusnya tidak memiliki pengetahuan bahwa yang lain adalah sesuatu. Inilah sebabnya kami membuat objek tingkat tinggi untuk mengatur cara menyatukan objek sederhana.
Tezra
@ Teezra Tapi kemudian, bukankah benda-benda tingkat tinggi ini tidak dapat digunakan kembali sama sekali? Rasanya seperti benda-benda ini bertindak sebagai fungsi, mereka hanya ada sebagai prosedur yang mereka tunjukkan.
Steve Chamaillard
@SteveChamaillard Setiap program akan memiliki setidaknya sedikit logika spesifik yang tidak masuk akal dalam program lain, tetapi idenya adalah untuk menjaga logika ini terisolasi ke beberapa kelas tingkat tinggi. Jika Anda memiliki mario, musuh, dan kelas level, Anda dapat menggunakan kembali mario dan musuh di gim lain. Jika Anda mengikat musuh dan mario secara langsung satu sama lain, maka dari game mana pun yang dibutuhkan seseorang harus menarik yang lain juga.
Tezra
0

Anda dapat mengaktifkan kopling longgar dengan menerapkan pola mediator. Menerapkannya mengharuskan Anda untuk memiliki referensi ke komponen mediator yang mengetahui semua komponen penerima.

Ini menyadari "pemikiran permainan, tolong biarkan ini dan itu terjadi".

Pola umum adalah pola terbitkan-berlangganan. Sangat tepat jika mediator tidak mengandung banyak logika. Kalau tidak, gunakan mediator kerajinan tangan yang tahu ke mana harus merutekan semua panggilan dan mungkin bahkan memodifikasinya.

Ada varian sinkron dan asinkron yang biasanya dinamai bus peristiwa, bus pesan, atau antrian pesan. Carilah mereka untuk menentukan apakah mereka sesuai dalam kasus khusus Anda.

Pahlawan Pengembara
sumber