Saya menemukan pohon warisan di basis kode kami (agak besar) yang kira-kira seperti ini:
public class NamedEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class OrderDateInfo : NamedEntity { }
Dari apa yang bisa saya kumpulkan, ini terutama digunakan untuk mengikat barang di front-end.
Bagi saya, ini masuk akal karena memberi nama konkret ke kelas, daripada mengandalkan generik NamedEntity
. Di sisi lain, ada sejumlah kelas yang tidak memiliki properti tambahan.
Apakah ada kelemahan dari pendekatan ini?
object-oriented-design
inheritance
robotron
sumber
sumber
OrderDateInfo
s dari orang-orang yang relevan dengan lainnyaNamedEntity
sIdentifier
kelas yang tidak ada hubungannya denganNamedEntity
tetapi membutuhkan propertiId
danName
properti, Anda tidak akan menggunakannyaNamedEntity
sebagai gantinya. Konteks dan penggunaan kelas yang tepat lebih dari sekedar properti dan metode yang dimilikinya.Jawaban:
Pendekatannya tidak buruk, tetapi ada solusi yang lebih baik tersedia. Singkatnya, sebuah antarmuka akan menjadi solusi yang jauh lebih baik untuk ini. Alasan utama mengapa antarmuka dan pewarisan berbeda di sini adalah karena Anda hanya dapat mewarisi dari satu kelas, tetapi Anda dapat mengimplementasikan banyak antarmuka .
Misalnya, pertimbangkan bahwa Anda telah menamai entitas, dan entitas yang diaudit. Anda memiliki beberapa entitas:
One
bukan entitas yang diaudit atau entitas yang dinamai. Itu sederhana:Two
adalah entitas bernama tetapi bukan entitas yang diaudit. Pada dasarnya itulah yang Anda miliki sekarang:Three
adalah entri yang dinamai dan diaudit. Di sinilah Anda mengalami masalah. Anda dapat membuatAuditedEntity
kelas dasar, tetapi Anda tidak dapat membuatThree
mewarisi baikAuditedEntity
danNamedEntity
:Namun, Anda mungkin memikirkan solusinya dengan
AuditedEntity
mewarisi dariNamedEntity
. Ini adalah retasan cerdas untuk memastikan bahwa setiap kelas hanya perlu mewarisi (langsung) dari satu kelas lain.Ini masih berfungsi. Tetapi apa yang telah Anda lakukan di sini dinyatakan bahwa setiap entitas yang diaudit secara inheren juga merupakan entitas yang dinamai . Yang membawa saya ke contoh terakhir saya.
Four
adalah entitas yang diaudit tetapi bukan entitas yang bernama. Tetapi Anda tidak dapat membiarkanFour
warisan dariAuditedEntity
karena Anda juga akan membuatnya menjadiNamedEntity
warisan karena antara AuditedEntityand
NamedEntity`.Menggunakan warisan, tidak ada cara untuk membuat keduanya
Three
danFour
berfungsi kecuali Anda mulai menduplikasi kelas (yang membuka set masalah yang sama sekali baru).Menggunakan antarmuka, ini dapat dengan mudah dicapai:
Satu-satunya kelemahan kecil di sini adalah Anda masih harus mengimplementasikan antarmuka. Tetapi Anda mendapatkan semua manfaat dari memiliki jenis yang dapat digunakan kembali secara umum, tanpa ada kelemahan yang muncul saat Anda memerlukan variasi pada beberapa jenis umum untuk entitas tertentu.
Tapi polimorfisme Anda tetap utuh:
Ini adalah variasi pada pola antarmuka marker , di mana Anda mengimplementasikan antarmuka kosong murni untuk dapat menggunakan jenis antarmuka untuk memeriksa apakah kelas yang diberikan "ditandai" dengan antarmuka ini.
Anda menggunakan kelas bawaan alih-alih antarmuka yang diimplementasikan, tetapi tujuannya sama, jadi saya akan menyebutnya sebagai "kelas bertanda".
Pada nilai nominal, tidak ada yang salah dengan antarmuka / kelas marker. Mereka secara sintaksis dan teknis sah, dan tidak ada kekurangan inheren untuk menggunakannya asalkan penanda secara universal benar (pada waktu kompilasi) dan tidak bersyarat .
Ini adalah persis bagaimana Anda harus membedakan antara pengecualian yang berbeda, bahkan ketika pengecualian itu sebenarnya tidak memiliki sifat / metode tambahan dibandingkan dengan metode dasar.
Jadi pada dasarnya tidak ada yang salah dengan melakukannya, tetapi saya akan menyarankan menggunakan ini dengan hati-hati, memastikan bahwa Anda tidak hanya mencoba untuk menutupi kesalahan arsitektur yang ada dengan polimorfisme yang dirancang dengan buruk.
sumber
three
dalam contoh Anda dari perangkap tidak menggunakan antarmuka sebenarnya berlaku di C ++ misalnya, sebenarnya ini berfungsi dengan baik untuk keempat contoh. Sebenarnya ini semua tampaknya menjadi pembatasan C # sebagai bahasa daripada kisah peringatan tidak menggunakan warisan ketika Anda harus menggunakan antarmuka.Ini adalah sesuatu yang saya gunakan untuk mencegah polimorfisme digunakan.
Katakanlah Anda memiliki 15 kelas berbeda yang memiliki
NamedEntity
sebagai kelas dasar di suatu tempat dalam rantai pewarisan mereka dan Anda sedang menulis metode baru yang hanya berlaku untukOrderDateInfo
.Anda "bisa" hanya menulis tanda tangan sebagai
void MyMethodThatShouldOnlyTakeOrderDateInfos(NamedEntity foo)
Dan berharap dan berdoa tidak ada yang menyalahgunakan sistem tipe untuk mendorong
FooBazNamedEntity
di sana.Atau Anda "bisa" menulis saja
void MyMethod(OrderDateInfo foo)
. Sekarang dipaksakan oleh kompiler. Sederhana, anggun dan tidak bergantung pada orang yang tidak membuat kesalahan.Juga, seperti yang ditunjukkan @candied_orange , pengecualian adalah kasus yang bagus untuk ini. Sangat jarang (dan maksud saya sangat, sangat, sangat jarang) Anda ingin menangkap semuanya
catch (Exception e)
. Kemungkinan besar Anda ingin menangkapSqlException
atauFileNotFoundException
atau pengecualian khusus untuk aplikasi Anda. Kelas-kelas itu sering kali tidak memberikan data atau fungsionalitas lebih banyak daripadaException
kelas dasar , tetapi mereka memungkinkan Anda untuk membedakan apa yang mereka wakili tanpa harus memeriksanya dan memeriksa bidang jenis atau mencari teks tertentu.Secara keseluruhan, ini adalah trik untuk mendapatkan sistem tipe yang memungkinkan Anda untuk menggunakan set tipe yang lebih sempit daripada yang Anda bisa jika Anda menggunakan kelas dasar. Maksud saya, Anda bisa mendefinisikan semua variabel dan argumen Anda memiliki tipe
Object
, tetapi itu hanya akan membuat pekerjaan Anda lebih sulit, bukan?sumber
NamedEntity
untuk ini, misalnya untukEntityName
, sehingga komposisi masuk akal ketika dijelaskan.Ini adalah pewarisan favorit saya. Saya menggunakannya sebagian besar untuk pengecualian yang bisa menggunakan nama yang lebih baik, lebih spesifik
Masalah warisan yang biasanya mengarah ke rantai panjang dan menyebabkan masalah yo-yo tidak berlaku di sini karena tidak ada yang memotivasi Anda untuk rantai.
sumber
IMHO desain kelas salah. Harus.
OrderDateInfo
HAS AName
adalah hubungan yang lebih alami dan menciptakan dua kelas yang mudah dipahami yang tidak akan memancing pertanyaan awal.Metode apa pun yang diterima
NamedEntity
sebagai parameter seharusnya hanya tertarik padaId
danName
properti, sehingga metode tersebut harus diubah untuk menerimaEntityName
.Satu-satunya alasan teknis yang saya terima untuk desain aslinya adalah untuk pengikatan properti, yang disebutkan OP. Kerangka omong kosong tidak akan mampu mengatasi properti tambahan dan mengikatnya
object.Name.Id
. Tetapi jika kerangka kerja mengikat Anda tidak dapat mengatasinya maka Anda memiliki lebih banyak utang teknologi untuk ditambahkan ke daftar.Saya akan mengikuti jawaban @ Flater, tetapi dengan banyak antarmuka yang mengandung properti Anda akhirnya menulis banyak kode boilerplate, bahkan dengan sifat otomatis C # yang indah. Bayangkan melakukannya di Jawa!
sumber
Kelas memperlihatkan perilaku, dan Struktur Data mengekspos data.
Saya melihat kata kunci kelas, tetapi saya tidak melihat perilaku apa pun. Ini berarti bahwa saya akan mulai melihat kelas ini sebagai struktur data. Dalam nada ini, saya akan ulangi pertanyaan Anda sebagai
Jadi, Anda dapat menggunakan tipe data tingkat atas. Ini memungkinkan memanfaatkan sistem tipe untuk memastikan kebijakan di set besar struktur data yang berbeda dengan memastikan semua properti ada di sana.
Jadi, Anda dapat menggunakan tipe data level yang lebih rendah. Ini memungkinkan memasukkan petunjuk ke dalam sistem pengetikan untuk mengekspresikan tujuan variabel
Dalam hierarki di atas, kami merasa nyaman untuk menentukan bahwa Seseorang dinamai, sehingga orang dapat memperoleh dan mengubah nama mereka. Meskipun mungkin masuk akal untuk menambahkan properti tambahan ke
Person
struktur data, masalah yang kami selesaikan tidak memerlukan pemodelan Person yang sempurna, dan karenanya kami lalai untuk menambahkan properti umum sepertiage
, dll.Jadi ini adalah pengungkit dari sistem pengetikan untuk mengekspresikan maksud dari item Bernama ini dengan cara yang tidak putus dengan pembaruan (seperti dokumentasi) dan dapat diperpanjang pada tanggal yang terakhir dengan mudah (jika Anda merasa Anda benar-benar membutuhkan
age
bidang nanti ).sumber