Saya lebih suka fitur OOP ketika mengembangkan game dengan Unity. Saya biasanya membuat kelas dasar (sebagian besar abstrak) dan menggunakan objek warisan untuk berbagi fungsi yang sama ke berbagai objek lainnya.
Namun, saya baru-baru ini mendengar dari seseorang bahwa menggunakan warisan harus dihindari, dan kita harus menggunakan antarmuka saja. Jadi saya bertanya mengapa, dan dia berkata bahwa "Benda warisan tentu saja penting, tetapi jika ada banyak objek yang diperluas, hubungan antar kelas sangat dalam.
Saya menggunakan kelas dasar abstrak, sesuatu seperti WeaponBase
dan menciptakan kelas senjata tertentu seperti Shotgun
, AssaultRifle
, Machinegun
sesuatu seperti itu. Ada begitu banyak keuntungan, dan satu pola yang sangat saya nikmati adalah polimorfisme. Saya dapat memperlakukan objek yang dibuat oleh subclass seolah-olah mereka adalah kelas dasar, sehingga logikanya dapat dikurangi secara drastis dan dibuat lebih dapat digunakan kembali. Jika saya perlu mengakses bidang atau metode dari sub-kelas, saya melemparkan kembali ke sub-kelas.
Saya tidak ingin mendefinisikan bidang yang sama, yang biasa digunakan dalam kelas yang berbeda, seperti AudioSource
/ Rigidbody
/ Animator
dan banyak bidang anggota I didefinisikan seperti fireRate
, damage
, range
, spread
dan sebagainya. Juga dalam beberapa kasus, beberapa metode dapat ditimpa. Jadi saya menggunakan metode virtual dalam kasus itu, sehingga pada dasarnya saya bisa memanggil metode itu menggunakan metode dari kelas induk, tetapi jika logikanya harus berbeda pada anak, saya telah secara manual menimpa mereka.
Jadi, haruskah semua hal ini ditinggalkan sebagai praktik buruk? Haruskah saya menggunakan antarmuka saja?
sumber
Jawaban:
Mendukung komposisi daripada warisan dalam entitas Anda dan sistem inventaris / item. Saran ini cenderung berlaku untuk struktur logika game, ketika cara Anda dapat merakit hal-hal yang kompleks (saat runtime) dapat menyebabkan banyak kombinasi yang berbeda; saat itulah kami lebih suka komposisi.
Pilih warisan daripada komposisi dalam logika tingkat aplikasi Anda, mulai dari konstruksi UI hingga layanan. Sebagai contoh,
Widget->Button->SpinnerButton
atauConnectionManager->TCPConnectionManager
vs->UDPConnectionManager
.... ada hirarki derivasi yang didefinisikan dengan jelas di sini, daripada banyak derivasi potensial, jadi lebih mudah untuk menggunakan warisan.
Intinya : gunakan warisan di mana Anda bisa, tetapi gunakan komposisi di mana Anda harus. PS Alasan lain kita dapat mendukung komposisi dalam sistem entitas adalah bahwa biasanya ada banyak entitas, dan warisan dapat menimbulkan biaya kinerja untuk mengakses anggota pada setiap objek; lihat tabel .
sumber
P.S. The other reason we may favour composition in entity systems is ... performance
: Apakah ini benar? Melihat halaman WIkipedia yang ditautkan oleh @Linaith menunjukkan, bahwa Anda harus membuat objek antarmuka Anda. Karenanya, Anda memiliki (masih atau bahkan lebih) panggilan fungsi virtual dan lebih banyak cache misses, saat Anda memperkenalkan tingkat tipuan lainnya.Anda sudah mendapatkan beberapa jawaban yang bagus, tetapi gajah besar di ruangan dalam pertanyaan Anda adalah yang ini:
Sebagai aturan praktis, ketika seseorang memberi Anda aturan praktis, maka abaikan saja. Ini tidak hanya berlaku untuk "seseorang memberi tahu Anda sesuatu", tetapi juga untuk membaca hal-hal di internet. Kecuali Anda tahu mengapa (dan dapat benar-benar berdiri di belakangnya), nasihat semacam itu tidak berharga dan seringkali sangat berbahaya.
Dalam pengalaman saya, konsep yang paling penting, dan membantu dalam OOP adalah "kopling rendah" dan "kohesi tinggi" (kelas / objek tahu sesedikit mungkin tentang satu sama lain, dan setiap unit bertanggung jawab atas sesedikit mungkin hal).
Kopling rendah
Ini berarti bahwa setiap "bundel barang" dalam kode Anda harus bergantung pada lingkungannya sesedikit mungkin. Ini berlaku untuk kelas (desain kelas) tetapi juga objek (implementasi aktual), "file" secara umum (yaitu, jumlah
#include
s per satu.cpp
file , jumlahimport
per.java
file tunggal dan sebagainya).Tanda bahwa dua entitas digabungkan adalah bahwa salah satu dari mereka akan rusak (atau perlu diubah) ketika yang lain diubah dengan cara apa pun.
Warisan meningkatkan sambungan, jelas; mengubah kelas dasar mengubah semua subclass.
Antarmuka mengurangi kopling: dengan mendefinisikan kontrak berbasis metode yang jelas, Anda dapat mengubah apa pun tentang kedua sisi antarmuka secara bebas, selama Anda tidak mengubah kontrak. (Perhatikan bahwa "antarmuka" adalah konsep umum,
interface
kelas abstrak Java atau C ++ hanyalah detail implementasi).Kohesi Tinggi
Ini berarti setiap kelas, objek, file dll. Menjadi perhatian atau bertanggung jawab untuk sesedikit mungkin. Yaitu, hindari kelas besar yang melakukan banyak hal. Dalam contoh Anda, jika senjata Anda memiliki aspek yang benar-benar terpisah (amunisi, perilaku menembak, representasi grafis, representasi inventaris, dll.), Maka Anda dapat memiliki kelas berbeda yang mewakili tepat satu dari hal-hal itu. Kelas senjata utama kemudian berubah menjadi "pemegang" perincian itu; objek senjata kemudian sedikit lebih dari beberapa petunjuk untuk detail itu.
Dalam contoh ini, Anda akan memastikan bahwa kelas Anda yang mewakili "Perilaku Menembak" tahu sesedikit mungkin secara manusiawi tentang kelas senjata utama. Secara optimal, tidak ada sama sekali. Misalnya, ini berarti bahwa Anda dapat memberikan "Perilaku Menembak" ke objek apa pun di dunia Anda (menara, gunung berapi, NPC ...) hanya dengan satu jentikan jari. Jika suatu saat Anda ingin mengubah cara senjata direpresentasikan dalam inventaris, maka Anda dapat melakukannya - hanya kelas inventaris Anda yang mengetahui hal itu sama sekali.
Tanda bahwa suatu entitas tidak kohesif adalah jika ia tumbuh lebih besar dan lebih besar, bercabang ke beberapa arah secara bersamaan.
Warisan seperti yang Anda gambarkan mengurangi kohesi - kelas senjata Anda, pada akhirnya, adalah bongkahan besar yang menangani segala macam aspek yang berbeda, yang tidak terkait dengan senjata Anda.
Antarmuka secara tidak langsung meningkatkan kohesi dengan secara jelas memisahkan tanggung jawab antara kedua sisi antarmuka.
Apa yang harus dilakukan sekarang
Masih belum ada aturan yang keras dan cepat, semua ini hanyalah pedoman. Secara umum, sebagai pengguna TKK yang disebutkan dalam jawabannya, warisan diajarkan banyak di sekolah dan buku; itu adalah hal-hal mewah tentang OOP. Antarmuka mungkin lebih membosankan untuk diajarkan, dan juga (jika Anda melewati contoh-contoh sepele) sedikit lebih sulit, membuka bidang injeksi ketergantungan, yang tidak begitu jelas sebagai warisan.
Pada akhirnya, skema berbasis warisan Anda masih lebih baik daripada tidak memiliki desain OOP yang jelas. Jadi jangan ragu untuk tetap menggunakannya. Jika Anda mau, Anda dapat merenungkan / google sedikit tentang Kopling Rendah, Kohesi Tinggi dan lihat apakah Anda ingin menambahkan pemikiran semacam itu ke gudang senjata Anda. Anda selalu dapat menolak untuk mencobanya jika ingin, nanti; atau coba pendekatan berbasis antarmuka pada modul kode baru berikutnya yang lebih besar.
sumber
Gagasan bahwa pewarisan harus dihindari sama sekali salah.
Ada prinsip pengkodean yang disebut Composition over Inheritance . Dikatakan bahwa Anda dapat mencapai hal yang sama dengan komposisi, dan itu lebih disukai, karena Anda dapat menggunakan kembali beberapa kode. Lihat Mengapa saya lebih suka komposisi daripada warisan?
Saya harus mengatakan saya suka kelas senjatamu dan akankah ia melakukan hal yang sama. Tapi saya belum membuat game sekarang ...Seperti yang ditunjukkan oleh James Trotter, komposisi dapat memiliki beberapa keuntungan, terutama dalam fleksibilitas saat runtime untuk mengubah cara kerja senjata. Ini mungkin dengan warisan, tetapi lebih sulit.
sumber
Masalahnya adalah bahwa warisan mengarah ke penggandengan - objek Anda perlu tahu lebih banyak tentang satu sama lain. Itu sebabnya aturannya adalah "Selalu mendukung komposisi daripada warisan". Ini tidak berarti TIDAK PERNAH menggunakan warisan, itu berarti menggunakannya di tempat yang benar-benar sesuai, tetapi jika Anda pernah duduk di sana berpikir "Saya bisa melakukan ini dengan dua cara dan keduanya masuk akal", langsung saja ke komposisi.
Warisan juga bisa menjadi semacam pembatas. Anda memiliki "WeaponBase" yang bisa menjadi AssultRifle - dahsyat. Apa yang terjadi ketika Anda memiliki senapan laras ganda dan ingin membiarkan laras menyala secara mandiri - sedikit lebih keras tetapi bisa dilakukan, Anda hanya memiliki kelas laras tunggal dan laras ganda, tetapi Anda tidak bisa hanya memasang 2 barel tunggal dengan senjata yang sama, bukan? Bisakah Anda mod satu untuk memiliki 3 barel atau apakah Anda memerlukan kelas baru untuk itu?
Bagaimana dengan AssultRifle dengan GrenadeLauncher di bawahnya - hmm, sedikit lebih keras. Bisakah Anda mengganti GrenadeLauncher dengan senter untuk berburu malam hari?
Terakhir, bagaimana Anda mengizinkan pengguna membuat senjata sebelumnya dengan mengedit file konfigurasi? Ini sulit karena Anda memiliki hubungan kode-keras yang mungkin lebih baik disusun dan dikonfigurasi dengan data.
Warisan berganda dapat memperbaiki beberapa contoh sepele ini, tetapi menambah beberapa masalah.
Sebelum ada perkataan umum seperti "Lebih suka komposisi daripada warisan", saya menemukan ini dengan menulis pohon warisan yang terlalu rumit (yang menyenangkan dan bekerja dengan sempurna) kemudian menemukan bahwa itu benar-benar sulit untuk dimodifikasi dan dipelihara kemudian. Perkataan ini hanya supaya Anda memiliki cara alternatif dan ringkas untuk belajar dan mengingat konsep seperti itu tanpa harus melalui seluruh pengalaman - tetapi jika Anda ingin menggunakan warisan secara besar-besaran, saya sarankan untuk tetap berpikiran terbuka dan mengevaluasi seberapa baik kerjanya untuk Anda - tidak ada hal buruk yang akan terjadi jika Anda menggunakan banyak warisan dan itu mungkin merupakan pengalaman belajar yang hebat paling buruk (paling-paling itu mungkin bekerja dengan baik untuk Anda)
sumber
Monster
subkelas dariWalkingEntity
. Kemudian mereka menambahkan slime. Menurut Anda seberapa baik itu berhasil?Bertentangan dengan jawaban yang lain, ini tidak ada hubungannya dengan pewarisan vs komposisi. Warisan vs komposisi adalah keputusan yang Anda buat tentang bagaimana kelas akan diimplementasikan. Antarmuka vs kelas adalah keputusan yang datang sebelumnya.
Antarmuka adalah warga negara kelas satu dari bahasa OOP. Kelas adalah detail implementasi sekunder. Pikiran programmer baru sangat dilanggar oleh guru dan buku yang memperkenalkan kelas dan warisan sebelum antarmuka.
Prinsip kuncinya adalah bahwa bila memungkinkan, jenis semua parameter metode dan nilai pengembalian harus berupa antarmuka, bukan kelas. Ini membuat API Anda jauh lebih fleksibel dan kuat. Sebagian besar waktu, metode Anda dan penelepon mereka seharusnya tidak memiliki alasan untuk mengetahui atau peduli tentang kelas sebenarnya dari objek yang mereka hadapi. Jika API Anda agnostik tentang detail implementasi, Anda dapat dengan bebas beralih antara komposisi dan pewarisan tanpa merusak apa pun.
sumber
interface
(kata kunci bahasa).Setiap kali seseorang memberi tahu Anda bahwa satu pendekatan spesifik adalah yang terbaik untuk semua kasus, itu sama dengan memberi tahu Anda bahwa satu dan obat yang sama menyembuhkan semua penyakit.
Warisan vs komposisi adalah pertanyaan is-a vs has-a. Antarmuka adalah cara lain (ke-3), cocok untuk beberapa situasi.
Inheritance, atau is-a logic: Anda menggunakannya ketika kelas baru akan berperilaku dan digunakan sepenuhnya seperti (secara lahiriah) kelas lama tempat Anda berasal, jika kelas baru akan diekspos ke publik semua fungsi yang dimiliki kelas lama ... lalu Anda warisi.
Komposisi, atau memiliki-logika: Jika kelas baru hanya perlu menggunakan kelas lama secara internal, tanpa memaparkan fitur kelas lama ke publik, maka Anda menggunakan komposisi - yaitu, memiliki instance dari kelas lama sebagai properti anggota atau variabel yang baru. (Ini bisa menjadi properti pribadi, atau dilindungi, atau apa pun, tergantung pada kasus penggunaan). Poin kunci di sini adalah bahwa ini adalah kasus penggunaan di mana Anda tidak ingin mengekspos fitur kelas asli dan digunakan kepada publik, cukup gunakan secara internal, sedangkan dalam kasus warisan Anda memaparkannya kepada publik, melalui kelas baru. Terkadang Anda membutuhkan satu pendekatan, dan terkadang yang lain.
Antarmuka: Antarmuka adalah untuk kasus penggunaan lain - ketika Anda ingin kelas baru untuk sebagian dan tidak sepenuhnya menerapkan dan mengekspos ke publik fungsi dari yang lama. Ini memungkinkan Anda memiliki kelas baru, kelas dari hierarki yang sama sekali berbeda dari yang lama, berperilaku seperti yang lama dalam beberapa aspek saja.
Katakanlah Anda memiliki berbagai macam makhluk yang diwakili oleh kelas, dan mereka memiliki fungsi yang diwakili oleh berbagai fungsi. Misalnya, burung akan memiliki Talk (), Fly () dan Poop (). Class Duck akan mewarisi dari Bird kelas, menerapkan semua fitur.
Class Fox, jelas, tidak bisa terbang. Jadi jika Anda mendefinisikan leluhur untuk memiliki semua fitur, maka Anda tidak dapat menurunkan keturunan dengan benar.
Namun, jika Anda memecah fitur menjadi grup, mewakili setiap kelompok panggilan dengan antarmuka, katakan, JIKA, berisi Takeoff (), FlapWings () dan Land (), maka Anda bisa untuk fungsi implementasi kelas Fox dari ITalk dan IPoop tapi tidak JIKA. Anda kemudian akan mendefinisikan variabel dan parameter untuk menerima objek yang mengimplementasikan antarmuka spesifik, dan kemudian kode yang bekerja dengan mereka akan tahu apa yang bisa dipanggil ... dan selalu dapat meminta untuk antarmuka lain, jika perlu melihat apakah fungsi lain juga diimplementasikan untuk objek saat ini.
Masing-masing pendekatan ini memiliki kasus penggunaan ketika itu yang terbaik, tidak ada pendekatan yang merupakan solusi terbaik mutlak untuk semua kasus.
sumber
Untuk gim, terutama dengan Unity, yang berfungsi dengan arsitektur entitas-komponen, Anda harus memilih komposisi daripada pewarisan untuk komponen gim Anda. Ini jauh lebih fleksibel, dan menghindari masuk ke situasi di mana Anda ingin entitas game "menjadi" dua hal yang berada di daun berbeda dari pohon warisan.
Jadi, misalnya, katakan Anda memiliki TalkingAI di satu cabang pohon warisan, dan VolumetricCloud di cabang lain yang terpisah, dan Anda ingin cloud yang berbicara. Ini sulit dengan pohon warisan yang dalam. Dengan entitas-komponen, Anda hanya membuat entitas yang memiliki komponen TalkingAI dan Cloud, dan Anda siap melakukannya.
Tetapi itu tidak berarti bahwa dalam, katakanlah, implementasi cloud volumetrik Anda, Anda seharusnya tidak menggunakan warisan. Kode untuk itu mungkin substansial dan terdiri dari beberapa kelas, dan Anda dapat menggunakan OOP sesuai kebutuhan. Tapi itu semua akan berjumlah satu komponen game.
Sebagai catatan, saya mengambil beberapa masalah dengan ini:
Warisan bukan untuk penggunaan kembali kode. Ini untuk membangun hubungan is-a . Banyak waktu ini berjalan seiring, tetapi Anda harus berhati-hati. Hanya karena dua modul mungkin perlu menggunakan kode yang sama, itu tidak berarti bahwa satu dari jenis yang sama dengan yang lain.
Anda dapat menggunakan antarmuka jika Anda ingin, katakanlah, memiliki
List<IWeapon>
dengan berbagai jenis senjata di dalamnya. Dengan cara ini, Anda dapat mewarisi dari antarmuka IWeapon, dan subkelas MonoBehaviour untuk semua senjata, dan menghindari masalah apa pun dengan tidak adanya pewarisan berganda.sumber
Anda sepertinya tidak mengerti apa itu antarmuka atau tidak. Butuh waktu sekitar 10+ tahun untuk berpikir tentang OO dan mengajukan beberapa pertanyaan kepada orang-orang yang benar-benar pintar adalah apakah saya dapat memiliki momen ah-ha dengan antarmuka. Semoga ini membantu.
Yang terbaik adalah menggunakan kombinasi keduanya. Dalam OO mind modelling dunia dan orang-orang katakanlah, beberapa contoh yang mendalam. Jiwa dihubungkan dengan tubuh melalui pikiran. Pikiran ADALAH penghubung antara jiwa (beberapa menganggap intelek) dan perintah yang dikeluarkannya kepada tubuh. Antarmuka pikiran dengan tubuh adalah sama untuk semua orang, secara fungsional.
Analogi yang sama dapat digunakan antara orang (tubuh dan jiwa) yang berinteraksi dengan dunia. Tubuh adalah penghubung antara pikiran dan dunia. Semua orang berinteraksi dengan dunia dengan cara yang sama, satu-satunya keunikan adalah BAGAIMANA kami berinteraksi dengan dunia, yaitu bagaimana kami menggunakan PIKIRAN kami untuk MEMUTUSKAN BAGAIMANA kami berinteraksi / berinteraksi di dunia.
Sebuah antarmuka kemudian hanyalah sebuah mekanisme untuk menghubungkan struktur OO Anda dengan struktur OO lain yang berbeda dengan masing-masing pihak memiliki atribut yang berbeda yang harus bernegosiasi / memetakan satu sama lain untuk menghasilkan beberapa keluaran fungsional dalam media / lingkungan yang berbeda.
Jadi bagi pikiran untuk memanggil fungsi open-hand, ia harus mengimplementasikan interface nervous_command_system dengan BAHWA memiliki API sendiri dengan instruksi bagaimana mengimplementasikan semua persyaratan yang diperlukan sebelum memanggil open-hand.
sumber