Haruskah saya lebih suka komposisi atau warisan dalam skenario ini?

11

Pertimbangkan sebuah antarmuka:

interface IWaveGenerator
{
    SoundWave GenerateWave(double frequency, double lengthInSeconds);
}

Antarmuka ini diimplementasikan oleh sejumlah kelas yang menghasilkan gelombang dengan bentuk yang berbeda (misalnya, SineWaveGeneratordan SquareWaveGenerator).

Saya ingin menerapkan kelas yang menghasilkan SoundWaveberdasarkan pada data musik, bukan data suara mentah. Itu akan menerima nama catatan dan panjang dalam hal ketukan (bukan detik), dan secara internal menggunakan IWaveGeneratorfungsi untuk membuat yang SoundWavesesuai.

Pertanyaannya adalah, haruskah NoteGeneratormengandung IWaveGeneratoratau mewarisi dari IWaveGeneratorimplementasi?

Saya condong ke arah komposisi karena dua alasan:

1- Ini memungkinkan saya untuk menyuntikkan apapun IWaveGeneratorke NoteGeneratordinamis. Juga, saya hanya perlu satu NoteGeneratorkelas, bukan SineNoteGenerator, SquareNoteGenerator, dll

2- Tidak perlu untuk NoteGeneratormengekspos antarmuka tingkat bawah yang ditentukan oleh IWaveGenerator.

Namun saya memposting pertanyaan ini untuk mendengar pendapat lain tentang ini, mungkin poin yang belum saya pikirkan.

BTW: Saya akan mengatakan NoteGenerator secara konseptual IWaveGeneratorkarena menghasilkan SoundWaves.

Aviv Cohn
sumber

Jawaban:

14

Ini memungkinkan saya untuk menyuntikkan IWaveGenerator apa pun ke NoteGenerator secara dinamis. Juga, saya hanya perlu satu kelas NoteGenerator, bukan SineNoteGenerator , SquareNoteGenerator , dll.

Itu adalah tanda yang jelas akan lebih baik untuk komposisi penggunaan sini, dan tidak mewarisi dari SineGeneratoratau SquareGeneratoratau (lebih buruk) baik. Namun demikian, masuk akal untuk mewarisi NoteGenerator langsung dari IWaveGeneratorjika Anda mengubah yang terakhir.

Masalah sebenarnya di sini adalah, mungkin bermakna memiliki NoteGeneratormetode seperti

SoundWave GenerateWave(string noteName, double noOfBeats, IWaveGenerator waveGenerator);

tetapi tidak dengan metode

SoundWave GenerateWave(double frequency, double lengthInSeconds);

karena antarmuka ini terlalu spesifik. Anda ingin IWaveGenerators menjadi benda-benda yang menghasilkan SoundWaves, namun saat mengekspresikan antarmuka Anda IWaveGenerators adalah objek yang menghasilkan SoundWaves dari frekuensi dan panjang secara eksklusif . Jadi desain yang lebih baik seperti antarmuka seperti ini

interface IWaveGenerator
{
    SoundWave GenerateWave();
}

dan lewati parameter seperti frequencyatau lengthInSeconds, atau serangkaian parameter yang sama sekali berbeda melalui konstruktor a SineWaveGenerator, a SquareGenerator, atau generator lain apa pun yang ada dalam pikiran Anda. Ini akan memungkinkan Anda untuk membuat jenis lain IWaveGeneratordengan parameter konstruksi yang sama sekali berbeda. Mungkin Anda ingin menambahkan generator gelombang persegi panjang yang membutuhkan frekuensi dan dua parameter panjang, atau sesuatu seperti itu, mungkin Anda ingin menambahkan generator gelombang segitiga berikutnya, juga dengan setidaknya tiga parameter. Atau, sebuah NoteGenerator, dengan parameter konstruktor noteName, noOfBeatsdan waveGenerator.

Jadi solusi umum di sini adalah memisahkan parameter input dari fungsi output, dan membuat hanya bagian fungsi output dari antarmuka.

Doc Brown
sumber
Menarik, belum memikirkan hal ini. Tapi saya bertanya-tanya: apakah ini (mengatur 'parameter ke fungsi polimorfik' dalam konstruktor) sering berhasil dalam kenyataan? Karena kode itu memang harus tahu jenis apa yang dihadapinya, sehingga merusak Polimorfisme. Bisakah Anda memberi contoh di mana ini akan bekerja?
Aviv Cohn
2
@ AVVCohn: "kode itu memang harus tahu apa jenisnya" - tidak, itu adalah kesalahpahaman. Hanya bagian dari kode yang membangun tipe spesifik generator (mybe a factory), dan yang harus selalu tahu jenis apa yang berurusan dengannya.
Doc Brown
... dan jika Anda perlu membuat proses konstruksi objek Anda secara polimorfik, Anda dapat menggunakan pola "abstrak factory" ( en.wikipedia.org/wiki/Abstract_factory_pattern )
Doc Brown
Ini adalah solusi yang akan saya pilih. Kelas kecil dan tidak dapat diubah adalah cara yang tepat untuk pergi ke sini.
Stephen
9

Apakah NoteGenerator "konseptual" atau tidak, IWaveGenerator tidak masalah.

Anda hanya boleh mewarisi dari antarmuka jika Anda berencana untuk mengimplementasikan antarmuka yang tepat sesuai dengan Prinsip Pergantian Liskov, yaitu dengan semantik yang benar serta sintaks yang benar.

Kedengarannya seperti NoteGenerator Anda mungkin memiliki antarmuka yang sama secara sintaksis, tetapi semantiknya (dalam hal ini, arti dari parameter yang diperlukan) akan sangat berbeda, jadi menggunakan pewarisan dalam kasus ini akan sangat menyesatkan dan berpotensi rawan kesalahan. Anda berhak memilih komposisi di sini.

Ixrec
sumber
Sebenarnya saya tidak bermaksud NoteGeneratorakan mengimplementasikan GenerateWavetetapi menafsirkan parameter secara berbeda, ya saya setuju itu akan menjadi ide yang mengerikan. Maksud saya NoteGenerator adalah jenis spesialisasi generator gelombang: ia dapat menerima data input 'tingkat lebih tinggi' alih-alih hanya data suara mentah (mis. Nama catatan alih-alih frekuensi). Yaitu sineWaveGenerator.generate(440) == noteGenerator.generate("a4"). Maka datanglah pertanyaan, komposisi atau warisan.
Aviv Cohn
Jika Anda dapat membuat antarmuka tunggal yang sesuai dengan kelas generasi gelombang tinggi dan rendah, maka pewarisan dapat diterima. Tapi itu tampaknya sangat sulit, dan tidak mungkin memiliki manfaat nyata. Komposisi sepertinya merupakan pilihan yang lebih alami.
Ixrec
@ Isrec: sebenarnya, tidak terlalu sulit untuk memiliki antarmuka tunggal untuk semua jenis generator, OP mungkin harus melakukan keduanya, menggunakan komposisi untuk menyuntikkan generator tingkat rendah dan mewarisi dari antarmuka yang disederhanakan (tetapi tidak mewarisi NoteGenerator dari sebuah implementasi generator level rendah) Lihat jawaban saya.
Doc Brown
5

2 - NoteGenerator tidak perlu mengekspos antarmuka tingkat rendah yang ditentukan oleh IWaveGenerator.

Kedengarannya seperti NoteGeneratorbukan WaveGenerator, jadi karena itu seharusnya tidak mengimplementasikan antarmuka.

Komposisi adalah pilihan yang tepat.

Eric King
sumber
Saya akan mengatakan NoteGenerator secara konseptual IWaveGeneratorkarena menghasilkan SoundWaves.
Aviv Cohn
1
Jika tidak perlu diekspos GenerateWave, maka itu bukan IWaveGenerator. Tapi sepertinya menggunakan IWaveGenerator (mungkin lebih?), Maka komposisi.
Eric King
@EricKing: ini adalah jawaban yang benar selama seseorang harus tetap pada GenerateWavefungsi seperti yang tertulis dalam pertanyaan. Tapi dari komentar di atas saya kira bukan itu yang sebenarnya ada dalam pikiran OP.
Doc Brown
3

Anda memiliki wadah solid untuk komposisi. Anda mungkin memiliki kasus untuk juga menambahkan warisan. Cara mengetahuinya adalah dengan melihat kode panggilan. Jika Anda ingin dapat menggunakan NoteGeneratorkode panggilan yang ada yang mengharapkan IWaveGenerator, maka Anda perlu mengimplementasikan antarmuka. Anda sedang mencari kebutuhan akan substitusi. Apakah secara konseptual generator gelombang "is-a" ada di samping itu.

Karl Bielefeldt
sumber
Dalam hal itu, yaitu memilih komposisi, tetapi masih membutuhkan pewarisan itu untuk membuat substitusi terjadi, "pewarisan" akan dinamai eg IHasWaveGenerator, dan metode yang relevan pada antarmuka itu adalah GetWaveGeneratoryang mengembalikan instance IWaveGenerator. Tentu saja penamaan bisa diubah. (Saya hanya berusaha menyempurnakan detail - beri tahu saya jika detail saya salah.)
rwong
2

Tidak masalah untuk NoteGeneratormengimplementasikan antarmuka, dan juga, untuk NoteGeneratormemiliki implementasi internal yang merujuk (dengan komposisi) yang lain IWaveGenerator.

Secara umum, komposisi menghasilkan kode yang lebih mudah dikelola (yaitu dapat dibaca), karena Anda tidak memiliki kerumitan penggantian alasan. Pengamatan Anda tentang matriks kelas yang Anda miliki ketika menggunakan warisan juga tepat, dan mungkin dapat dianggap sebagai bau kode yang menunjuk ke arah komposisi.

Warisan lebih baik digunakan ketika Anda memiliki implementasi yang ingin Anda mengkhususkan atau sesuaikan, yang tampaknya tidak menjadi masalah di sini: Anda hanya perlu menggunakan antarmuka.

Erik Eidt
sumber
1
Tidak boleh NoteGeneratorditerapkan IWaveGeneratorkarena karena catatan memerlukan ketukan. bukan detik-.
Tulains Córdova
Ya, tentu saja jika tidak ada implementasi antarmuka yang masuk akal, maka kelas tidak boleh mengimplementasikannya. Namun, OP menyatakan bahwa "Saya akan mengatakan NoteGeneratorsecara konseptual sebuah IWaveGeneratorkarena ia menghasilkan SoundWaves", dan, ia sedang mempertimbangkan warisan, jadi saya mengambil kebebasan mental untuk kemungkinan bahwa mungkin ada beberapa implementasi antarmuka, meskipun ada lagi antarmuka atau tanda tangan yang lebih baik untuk kelas.
Erik Eidt