Pencarian cepat dari stackexchange ini menunjukkan bahwa secara umum komposisi umumnya dianggap lebih fleksibel daripada warisan tetapi seperti biasanya tergantung pada proyek dll dan ada kalanya pewarisan adalah pilihan yang lebih baik. Saya ingin membuat permainan catur 3D di mana setiap bagian memiliki jala, animasi yang mungkin berbeda dan sebagainya. Dalam contoh konkret ini, sepertinya Anda bisa memperdebatkan kasus untuk kedua pendekatan, apakah saya salah?
Warisan akan terlihat seperti ini (dengan konstruktor yang tepat, dll)
class BasePiece
{
virtual Squares GetValidMoveSquares() = 0;
Mesh* mesh;
// Other fields
}
class Pawn : public BasePiece
{
Squares GetValidMoveSquares() override;
}
yang tentu saja mematuhi prinsip "is-a" sedangkan komposisi akan terlihat seperti ini
class MovementComponent
{
virtual Squares GetValidMoveSquares() = 0;
}
class PawnMovementComponent
{
Squares GetValidMoveSquares() override;
}
enum class Type
{
PAWN,
BISHOP, //etc
}
class Piece
{
MovementComponent* movementComponent;
MeshComponent* mesh;
Type type;
// Other fields
}
Apakah ini lebih merupakan masalah pilihan pribadi atau apakah satu pendekatan jelas merupakan pilihan yang lebih cerdas daripada yang lain di sini?
EDIT: Saya pikir saya belajar sesuatu dari setiap jawaban jadi saya merasa tidak enak karena hanya memilih satu. Solusi terakhir saya akan mengambil inspirasi dari beberapa posting di sini (masih mengerjakannya). Terima kasih kepada semua orang yang meluangkan waktu untuk menjawab.
sumber
var Pawn = new Piece(howIMove, howITake, whatILookLike)
tampaknya jauh lebih sederhana, lebih mudah dikelola dan lebih bisa dikelola bagi saya daripada hierarki warisan.Jawaban:
Pada pandangan pertama, jawaban Anda berpura-pura solusi "komposisi" tidak menggunakan warisan. Tapi saya kira Anda lupa menambahkan ini:
(dan lebih banyak dari derivasi ini, satu untuk masing-masing dari enam jenis potongan). Sekarang ini lebih mirip pola "Strategi" klasik, dan juga memanfaatkan warisan.
Sayangnya, solusi ini memiliki kekurangan: masing-masing
Piece
sekarang menyimpan informasi jenisnya secara berlebihan dua kali:di dalam variabel anggota
type
di dalam
movementComponent
(diwakili oleh subtipe)Redundansi inilah yang benar-benar dapat membawa Anda ke masalah - kelas sederhana seperti
Piece
seharusnya menyediakan "sumber kebenaran tunggal", bukan dua sumber.Tentu saja, Anda dapat mencoba menyimpan informasi jenis hanya di
type
, dan tidak membuat kelas anakMovementComponent
juga. Tetapi desain ini kemungkinan besar akan mengarah padaswitch(type)
pernyataan " " yang sangat besar dalam implementasiGetValidMoveSquares
. Dan itu jelas merupakan indikasi kuat untuk pewarisan menjadi pilihan yang lebih baik di sini .Catatan dalam desain "pewarisan", cukup mudah untuk menyediakan bidang "tipe" dengan cara yang tidak berlebihan: menambahkan metode virtual
GetType()
keBasePiece
dan mengimplementasikannya di setiap kelas dasar.Mengenai "promosi": Saya di sini dengan @viden, saya menemukan argumen yang disajikan dalam jawaban @ TheCatWhisperer masih bisa diperdebatkan.
Menafsirkan "promosi" sebagai pertukaran fisik potongan alih-alih menafsirkannya sebagai perubahan jenis karya yang sama terasa lebih alami bagi saya. Jadi menerapkan ini seperti dalam cara yang sama - dengan menukar satu bagian dengan yang lain dari jenis yang berbeda - kemungkinan besar tidak akan menyebabkan masalah besar - setidaknya tidak untuk kasus catur tertentu.
sumber
movementComponent
. Lihat edit saya bagaimana menyediakan bidang jenis dengan cara yang aman dalam desain "warisan".Saya mungkin lebih suka opsi yang lebih sederhana kecuali jika Anda memiliki alasan yang jelas, diartikulasikan dengan baik, atau masalah ekstensiabilitas dan pemeliharaan yang kuat .
Opsi pewarisan adalah baris kode yang lebih sedikit dan kompleksitas yang lebih sedikit. Setiap jenis karya memiliki hubungan 1-ke-1 yang "tidak berubah" dengan karakteristik gerakannya. (Tidak seperti itu representasi, yang 1-ke-1, tetapi tidak selalu berubah - Anda mungkin ingin menawarkan berbagai "kulit".)
Jika Anda memecah hubungan 1-ke-1 yang tidak berubah antara bagian-bagian dan perilaku / gerakan mereka ke dalam beberapa kelas, Anda mungkin hanya menambahkan kompleksitas - dan tidak banyak lagi. Jadi, jika saya meninjau atau mewarisi kode itu, saya berharap dapat melihat alasan yang bagus dan terdokumentasi untuk kompleksitasnya.
Semua yang dikatakan, opsi yang bahkan lebih sederhana , IMO, adalah untuk membuat
Piece
antarmuka yang mengimplementasikan Potongan individu Anda . Dalam kebanyakan bahasa, ini sebenarnya sangat berbeda dari warisan , karena sebuah antarmuka tidak akan membatasi Anda untuk mengimplementasikan antarmuka lain . ... Anda hanya tidak mendapatkan perilaku kelas dasar secara gratis dalam hal ini: Anda ingin menempatkan perilaku bersama di tempat lain.sumber
Masalahnya dengan catur adalah bahwa aturan permainan dan permainan ditentukan dan diperbaiki. Desain apa pun yang berfungsi baik - gunakan apa pun yang Anda suka! Eksperimen dan coba semuanya.
Dalam dunia bisnis, bagaimanapun, tidak ada yang begitu ketat ditentukan seperti ini - aturan & persyaratan bisnis berubah seiring waktu, dan program harus berubah dari waktu ke waktu untuk mengakomodasi. Di sinilah mana-a vs memiliki-a vs data membuat perbedaan. Di sini, kesederhanaan membuat solusi kompleks lebih mudah untuk dipertahankan seiring waktu dan perubahan persyaratan. Secara umum, dalam bisnis, kita juga harus berurusan dengan kegigihan, yang mungkin melibatkan basis data juga. Jadi, kami memiliki aturan seperti jangan gunakan beberapa kelas saat satu kelas akan melakukan pekerjaan, dan, jangan gunakan warisan ketika komposisinya mencukupi. Tetapi semua aturan ini diarahkan untuk membuat kode dapat dipertahankan dalam jangka panjang dalam menghadapi perubahan peraturan & persyaratan - yang tidak terjadi pada catur.
Dengan catur, jalur perawatan jangka panjang yang paling mungkin adalah bahwa program Anda perlu menjadi lebih pintar dan lebih cerdas, yang pada akhirnya berarti kecepatan dan optimalisasi penyimpanan akan mendominasi. Untuk itu, Anda umumnya harus melakukan trade off yang mengorbankan keterbacaan untuk kinerja, dan bahkan desain OO terbaik pun pada akhirnya akan berjalan di pinggir jalan.
sumber
Saya akan mulai dengan membuat beberapa pengamatan tentang domain masalah (yaitu aturan catur):
Ini canggung untuk dimodelkan sebagai tanggung jawab atas karya itu sendiri, dan warisan maupun komposisi tidak terasa hebat di sini. Akan lebih alami untuk membuat model aturan ini sebagai warga negara kelas satu:
MovementRule
adalah antarmuka yang sederhana namun fleksibel yang memungkinkan implementasi untuk memperbarui status papan dengan cara apa pun yang diperlukan untuk mendukung gerakan kompleks seperti casting dan promosi. Untuk catur standar, Anda akan memiliki satu implementasi untuk setiap jenis permainan, tetapi Anda juga dapat memasukkan aturan yang berbeda dalam sebuahGame
contoh untuk mendukung varian catur yang berbeda.sumber
Saya akan berpikir dalam hal ini pewarisan akan menjadi pilihan yang lebih bersih. Komposisi mungkin sedikit lebih elegan, tetapi bagi saya tampaknya sedikit lebih dipaksakan.
Jika Anda berencana mengembangkan gim lain yang menggunakan gim bergerak yang berperilaku berbeda, komposisi mungkin merupakan pilihan yang lebih baik, terutama jika Anda menggunakan pola pabrik untuk menghasilkan gim yang diperlukan untuk setiap gim.
sumber
Benar, jadi Anda menggunakan warisan. Anda masih bisa mengarang dalam kelas Piece Anda, tetapi setidaknya Anda akan memiliki tulang punggung. Komposisi lebih fleksibel tetapi fleksibilitasnya berlebihan, secara umum, dan tentu saja dalam hal ini karena kemungkinan seseorang memperkenalkan jenis karya baru yang mengharuskan Anda untuk meninggalkan model kaku Anda adalah nol. Permainan catur tidak akan berubah dalam waktu dekat. Warisan memberi Anda model panduan, sesuatu untuk berhubungan juga, mengajarkan kode, arah. Komposisi tidak begitu banyak, entitas domain yang paling penting tidak menonjol, tidak berputar, Anda harus memahami permainan untuk memahami kode.
sumber
Tolong jangan gunakan warisan di sini. Sementara jawaban lain tentu saja memiliki banyak permata kebijaksanaan, menggunakan warisan di sini tentu akan menjadi kesalahan. Menggunakan komposisi tidak hanya membantu Anda menangani masalah yang tidak Anda antisipasi, tetapi saya sudah melihat masalah di sini bahwa warisan tidak dapat menangani dengan baik.
Promosi, ketika gadai dikonversi menjadi bagian yang lebih berharga, bisa jadi masalah pewarisan. Secara teknis, Anda bisa mengatasinya dengan mengganti satu bagian dengan yang lain, tetapi pendekatan ini memiliki keterbatasan. Salah satu batasan ini adalah pelacakan statistik per potong di mana Anda mungkin tidak ingin mengatur ulang informasi bagian pada promosi (atau menulis kode tambahan untuk menyalinnya).
Sekarang, sejauh desain komposisi Anda dalam pertanyaan Anda, saya pikir itu tidak perlu rumit. Saya tidak curiga Anda perlu memiliki komponen terpisah untuk setiap tindakan dan dapat menempel satu kelas per jenis per satuan. Jenis enum mungkin juga tidak dibutuhkan.
Saya akan mendefinisikan
Piece
kelas (seperti yang sudah Anda miliki), sertaPieceType
kelas. ThePieceType
kelas mendefinisikan semua metode yang pion, queen, dll harus menentukan, seperti,CanMoveTo
,GetPieceTypeName
, dll. Kemudian kelasPawn
danQueen
, dll akan benar-benar mewarisi dariPieceType
kelas.sumber
Piece
tidak virtual, saya setuju dengan solusi ini. Sementara desain kelas mungkin tampak sedikit lebih kompleks di permukaan, saya pikir implementasinya akan lebih sederhana secara keseluruhan. Hal-hal utama yang akan dikaitkan denganPiece
dan bukanPieceType
identitas itu dan berpotensi sejarah perpindahannya.Dari sudut pandang saya, dengan informasi yang diberikan, tidaklah bijaksana untuk menjawab apakah warisan atau komposisi harus menjadi jalan yang harus ditempuh.
Model Anda benar-benar berbeda, yang pertama Anda modelkan di sekitar konsep bidak catur, di yang kedua di sekitar konsep pergerakan bidak. Mereka mungkin secara fungsional setara, dan saya akan memilih yang mewakili domain dengan lebih baik dan membantu saya bernalar tentang hal itu dengan lebih mudah.
Juga, dalam desain kedua Anda, fakta bahwa
Piece
kelas Anda memilikitype
bidang, dengan jelas mengungkapkan ada masalah desain di sini karena karya itu sendiri dapat terdiri dari beberapa jenis. Bukankah itu terdengar seperti ini mungkin harus menggunakan semacam warisan, di mana karya itu sendiri adalah tipenya?Jadi, Anda tahu, sangat sulit untuk berdebat tentang solusi Anda. Yang penting bukanlah apakah Anda menggunakan warisan atau komposisi, tetapi apakah model Anda mencerminkan secara akurat domain masalah dan apakah itu berguna untuk bernalar tentang hal itu dan memberikan implementasi dari itu.
Butuh beberapa pengalaman untuk menggunakan pewarisan dan komposisi dengan benar, tetapi mereka adalah alat yang sama sekali berbeda dan saya tidak percaya seseorang dapat "menggantikan" yang satu dengan yang lain, meskipun saya dapat setuju bahwa suatu sistem dapat dirancang sepenuhnya menggunakan komposisi yang adil.
sumber
Yah, saya tidak menyarankan keduanya.
Sementara orientasi objek adalah alat yang bagus, dan sering membantu menyederhanakan banyak hal, itu bukan satu-satunya alat di gudang alat.
Pertimbangkan mengimplementasikan kelas papan sebagai gantinya, berisi bytearray sederhana.
Menafsirkannya, dan menghitung hal-hal di dalamnya dilakukan dalam fungsi anggota menggunakan bit-masking dan switching.
Gunakan MSB untuk pemiliknya, sisakan 7 bit untuk bagiannya, meskipun sisakan nol untuk yang kosong.
Atau, nol untuk kosong, tanda untuk pemilik, nilai absolut untuk potongan.
Jika Anda mau, Anda bisa menggunakan bitfields untuk menghindari penyembunyian manual, meskipun itu tidak dapat diport
sumber