Mengapa tidak mewarisi dari Daftar <T>?

1400

Ketika merencanakan program saya, saya sering memulai dengan rantai pemikiran seperti:

Tim sepak bola hanyalah daftar pemain sepakbola. Karena itu, saya harus mewakilinya dengan:

var football_team = new List<FootballPlayer>();

Urutan daftar ini mewakili urutan pemain terdaftar dalam daftar.

Tetapi saya menyadari kemudian bahwa tim juga memiliki properti lain, selain daftar pemain semata, yang harus dicatat. Misalnya, total skor berjalan musim ini, anggaran saat ini, warna seragam, a stringmewakili nama tim, dll.

Jadi saya berpikir:

Oke, tim sepak bola seperti daftar pemain, tetapi selain itu, ia memiliki nama (a string) dan total skor berjalan (an int). .NET tidak menyediakan kelas untuk menyimpan tim sepak bola, jadi saya akan membuat kelas sendiri. Struktur yang paling mirip dan relevan saat ini adalah List<FootballPlayer>, jadi saya akan mewarisi darinya:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

Tetapi ternyata suatu pedoman mengatakan Anda tidak seharusnya mewarisiList<T> . Saya benar-benar bingung dengan pedoman ini dalam dua hal.

Kenapa tidak?

Tampaknya Listentah bagaimana dioptimalkan untuk kinerja . Bagaimana? Masalah kinerja apa yang akan saya sebabkan jika saya memperpanjang List? Apa tepatnya yang akan pecah?

Alasan lain yang saya lihat adalah yang Listdisediakan oleh Microsoft, dan saya tidak punya kendali atasnya, jadi saya tidak bisa mengubahnya nanti, setelah mengekspos "API publik" . Tetapi saya berjuang untuk memahami hal ini. Apa itu API publik dan mengapa saya harus peduli? Jika proyek saya saat ini tidak dan sepertinya tidak pernah memiliki API publik ini, dapatkah saya dengan aman mengabaikan panduan ini? Jika saya memang mewarisi List dan ternyata saya memerlukan API publik, kesulitan apa yang akan saya miliki?

Mengapa itu penting? Daftar adalah daftar. Apa yang mungkin bisa berubah? Apa yang mungkin ingin saya ubah?

Dan terakhir, jika Microsoft tidak ingin saya mewarisi List, mengapa mereka tidak membuat kelas sealed?

Apa lagi yang harus saya gunakan?

Rupanya, untuk koleksi khusus, Microsoft telah menyediakan Collectionkelas yang seharusnya diperluas List. Tetapi kelas ini sangat telanjang, dan tidak memiliki banyak hal berguna, sepertiAddRange misalnya. Jawaban jvitor83 memberikan alasan kinerja untuk metode tertentu, tetapi bagaimana lambat AddRangetidak lebih baik daripada tidak AddRange?

Mewarisi dari Collectionjauh lebih banyak pekerjaan daripada mewarisi List, dan saya tidak melihat manfaatnya. Tentunya Microsoft tidak akan mengatakan kepada saya untuk melakukan pekerjaan ekstra tanpa alasan, jadi saya tidak dapat menahan perasaan bahwa saya entah bagaimana salah memahami sesuatu, dan mewarisi Collectionsebenarnya bukanlah solusi yang tepat untuk masalah saya.

Saya telah melihat saran seperti penerapan IList. Tidak, tidak. Ini adalah puluhan baris kode boilerplate yang tidak menguntungkan saya.

Terakhir, beberapa menyarankan membungkus Listsesuatu:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

Ada dua masalah dengan ini:

  1. Itu membuat kode saya verbose tidak perlu. Sekarang saya harus menelepon, my_team.Players.Countbukan hanya my_team.Count. Untungnya, dengan C # saya dapat mendefinisikan pengindeks untuk membuat pengindeksan transparan, dan meneruskan semua metode internal List... Tapi itu banyak kode! Apa yang saya dapat untuk semua pekerjaan itu?

  2. Itu tidak masuk akal. Tim sepak bola tidak "memiliki" daftar pemain. Ini adalah daftar pemain. Anda tidak mengatakan "John McFootballer telah bergabung dengan pemain SomeTeam". Anda mengatakan "John telah bergabung dengan SomeTeam". Anda tidak menambahkan huruf ke "karakter string", Anda menambahkan huruf ke string. Anda tidak menambahkan buku ke buku perpustakaan, Anda menambahkan buku ke perpustakaan.

Saya menyadari bahwa apa yang terjadi "di bawah tenda" dapat dikatakan sebagai "menambahkan X ke daftar internal Y", tetapi ini sepertinya cara berpikir yang sangat kontra-intuitif tentang dunia.

Pertanyaan saya (dirangkum)

Apa C # cara yang benar mewakili struktur data, yang, "logis" (yang mengatakan, "untuk pikiran manusia") hanya listdari thingsdengan beberapa lonceng dan peluit?

Apakah mewarisi dari List<T>selalu tidak dapat diterima? Kapan itu bisa diterima? Kenapa / mengapa tidak? Apa yang harus dipertimbangkan oleh seorang programmer, ketika memutuskan apakah akan mewarisi List<T>atau tidak?

Hebat
sumber
90
Jadi, apakah pertanyaan Anda benar-benar kapan harus menggunakan warisan (Kotak adalah-Persegi Panjang karena selalu masuk akal untuk menyediakan Kotak saat Persegi Panjang generik diminta) dan kapan menggunakan komposisi (FootballTeam memiliki-Daftar FootballPlayers, selain itu ke properti lain yang sama "mendasar" untuk itu, seperti nama)? Apakah programmer lain akan bingung jika di tempat lain dalam kode Anda memberikan mereka FootballTeam ketika mereka mengharapkan Daftar sederhana?
Penerbangan Odyssey
77
bagaimana jika saya memberi tahu Anda bahwa tim sepak bola adalah perusahaan bisnis yang MEMILIKI daftar kontrak pemain alih-alih daftar pemain kosong dengan beberapa properti tambahan?
STT LCU
75
Ini sangat sederhana. APAKAH tim sepak bola daftar pemain? Jelas tidak, karena seperti yang Anda katakan, ada properti lain yang relevan. Apakah tim sepak bola MEMILIKI daftar pemain? Ya, jadi harus berisi daftar. Selain itu, komposisi hanya disukai daripada warisan secara umum, karena lebih mudah untuk dimodifikasi nanti. Jika Anda menemukan pewarisan dan komposisi logis pada saat yang bersamaan, lanjutkan dengan komposisi.
Tobberoth
45
@FlightOdyssey: Misalkan Anda memiliki metode SquashRectangle yang mengambil persegi panjang, membagi dua ketinggiannya, dan menggandakan lebarnya. Sekarang lewati sebuah persegi panjang yang kebetulan 4x4 tetapi berjenis persegi panjang, dan persegi empat yang 4x4. Apa dimensi dari objek setelah SquashRectangle disebut? Untuk persegi panjang itu jelas 2x8. Jika kuadratnya 2x8 maka itu bukan lagi kuadrat; jika bukan 2x8 maka operasi yang sama pada bentuk yang sama menghasilkan hasil yang berbeda. Kesimpulannya adalah bahwa kotak yang bisa berubah-ubah bukanlah semacam persegi panjang.
Eric Lippert
29
@ Gangnus: Pernyataan Anda salah; subclass nyata diperlukan untuk memiliki fungsionalitas yang merupakan superset dari superclass. A stringdiperlukan untuk melakukan semua yang objectdapat dilakukan dan banyak lagi .
Eric Lippert

Jawaban:

1567

Ada beberapa jawaban bagus di sini. Saya akan menambahkan kepada mereka poin-poin berikut.

Apa cara C # yang benar dalam merepresentasikan struktur data, yang, "secara logis" (artinya, "bagi pikiran manusia") hanyalah daftar hal-hal dengan beberapa lonceng dan peluit?

Tanyakan kepada sepuluh orang yang bukan programmer komputer yang akrab dengan keberadaan sepak bola untuk mengisi formulir kosong:

Tim sepak bola adalah jenis khusus _____

Apakah ada yang mengatakan "daftar pemain sepak bola dengan beberapa lonceng dan peluit", atau mereka semua "tim olahraga" katakanlah atau "klub" atau "organisasi"? Gagasan Anda bahwa tim sepak bola adalah jenis daftar pemain tertentu ada di pikiran manusia Anda dan pikiran manusia Anda sendiri.

List<T>adalah sebuah mekanisme . Tim sepak bola adalah objek bisnis - yaitu, objek yang mewakili beberapa konsep yang ada dalam domain bisnis program. Jangan campur itu! Tim sepak bola adalah sejenis tim; ia memiliki daftar, daftar adalah daftar pemain . Daftar nama bukanlah daftar pemain tertentu . Daftar nama adalah daftar pemain. Jadi buatlah properti yang disebut Rostera List<Player>. Dan buat ReadOnlyList<Player>saat Anda sedang melakukannya, kecuali jika Anda percaya bahwa semua orang yang tahu tentang tim sepak bola dapat menghapus pemain dari daftar.

Apakah mewarisi dari List<T>selalu tidak dapat diterima?

Tidak dapat diterima oleh siapa? Saya? Tidak.

Kapan itu bisa diterima?

Ketika Anda sedang membangun mekanisme yang memperluas List<T>mekanisme .

Apa yang harus dipertimbangkan oleh seorang programmer, ketika memutuskan apakah akan mewarisi List<T>atau tidak?

Apakah saya membangun mekanisme atau objek bisnis ?

Tapi itu banyak kode! Apa yang saya dapat untuk semua pekerjaan itu?

Anda menghabiskan lebih banyak waktu mengetik pertanyaan Anda bahwa Anda perlu menulis metode penerusan untuk anggota yang relevan List<T>sebanyak lima puluh kali lipat. Anda jelas tidak takut pada kata kerja, dan kami berbicara tentang sejumlah kecil kode di sini; ini hanya beberapa menit.

MEMPERBARUI

Saya memikirkannya lagi dan ada alasan lain untuk tidak menjadikan tim sepak bola sebagai daftar pemain. Bahkan mungkin ide yang buruk untuk memodelkan tim sepak bola sebagai memiliki daftar pemain juga. Masalah dengan tim sebagai / memiliki daftar pemain adalah bahwa apa yang Anda dapatkan adalah potret tim pada saat tertentu . Saya tidak tahu apa kasus bisnis Anda untuk kelas ini, tetapi jika saya memiliki kelas yang mewakili tim sepak bola, saya ingin mengajukan pertanyaan seperti "berapa banyak pemain Seahawks yang melewatkan pertandingan karena cedera antara tahun 2003 dan 2013?" atau "Pemain Denver apa yang sebelumnya bermain untuk tim lain memiliki rekor lari yard-over-year terbesar?" atau " Apakah Piggers berjalan sepanjang tahun ini? "

Artinya, sebuah tim sepak bola bagi saya tampaknya dimodelkan dengan baik sebagai kumpulan fakta sejarah seperti ketika seorang pemain direkrut, cedera, pensiun, dll. Jelas daftar pemain saat ini adalah fakta penting yang mungkin harus menjadi depan dan- pusat, tetapi mungkin ada hal menarik lainnya yang ingin Anda lakukan dengan objek ini yang memerlukan perspektif yang lebih historis.

Eric Lippert
sumber
84
Meskipun jawaban yang lain sangat membantu, saya pikir yang satu ini menangani masalah saya secara langsung. Sedangkan untuk melebih-lebihkan jumlah kode, Anda benar bahwa pada akhirnya tidak terlalu banyak pekerjaan, tapi saya sangat mudah bingung ketika saya memasukkan beberapa kode dalam program saya tanpa memahami mengapa saya memasukkannya.
Biasa
128
@ Superbest: Senang saya bisa membantu! Anda benar untuk mendengarkan keraguan itu; jika Anda tidak mengerti mengapa Anda menulis beberapa kode, baik mencari tahu, atau menulis kode berbeda yang Anda mengerti.
Eric Lippert
48
@Mehrdad Tapi Anda sepertinya lupa aturan emas optimasi: 1) jangan lakukan itu, 2) jangan lakukan itu, dan 3) jangan lakukan itu tanpa terlebih dahulu melakukan profil kinerja untuk menunjukkan apa yang perlu dioptimalkan.
AJMansfield
107
@Mehrdad: Jujur, jika aplikasi Anda mengharuskan Anda peduli dengan beban kinerja, katakanlah, metode virtual, maka bahasa modern apa pun (C #, Java, dll) bukan bahasa untuk Anda. Saya memiliki laptop di sini yang dapat menjalankan simulasi setiap game arcade yang saya mainkan saat kanak-kanak secara bersamaan . Ini memungkinkan saya untuk tidak khawatir tentang tingkat tipuan di sana-sini.
Eric Lippert
44
@Superbest: Sekarang kita pasti berada di ujung "komposisi bukan warisan" spektrum. Roket terdiri dari bagian roket . Roket bukan "daftar jenis khusus"! Saya menyarankan kepada Anda bahwa Anda memotongnya lebih jauh; mengapa daftar, sebagai lawan dari urutanIEnumerable<T> - a ?
Eric Lippert
161

Wow, pos Anda memiliki banyak pertanyaan dan poin. Sebagian besar alasan yang Anda dapatkan dari Microsoft tepat pada titik. Mari kita mulai dengan segalanyaList<T>

  • List<T> adalah sangat optimal. Penggunaan utamanya adalah untuk digunakan sebagai anggota pribadi dari suatu objek.
  • Microsoft tidak menutup karena kadang-kadang Anda mungkin ingin membuat sebuah kelas yang memiliki nama ramah: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }. Sekarang semudah melakukannya var list = new MyList<int, string>();.
  • CA1002: Jangan memaparkan daftar generik : Pada dasarnya, bahkan jika Anda berencana untuk menggunakan aplikasi ini sebagai pengembang tunggal, ada baiknya untuk mengembangkan dengan praktik pengkodean yang baik, sehingga mereka ditanamkan ke Anda dan sifat dasar. Anda masih diperbolehkan mengekspos daftar sebagai IList<T>jika Anda membutuhkan konsumen untuk memiliki daftar yang diindeks. Ini memungkinkan Anda mengubah implementasi di dalam kelas nanti.
  • Microsoft dibuat Collection<T>sangat umum karena ini adalah konsep generik ... namanya mengatakan itu semua; itu hanya koleksi. Ada versi yang lebih tepat seperti SortedCollection<T>, ObservableCollection<T>, ReadOnlyCollection<T>, dll yang masing-masing melaksanakan IList<T>tapi tidak List<T> .
  • Collection<T>memungkinkan anggota (yaitu Tambah, Hapus, dll.) ditimpa karena mereka virtual. List<T>tidak.
  • Bagian terakhir dari pertanyaan Anda tepat. Tim sepakbola lebih dari sekadar daftar pemain, jadi itu harus kelas yang berisi daftar pemain itu. Pikirkan Komposisi vs Warisan . Sebuah tim sepak bola memiliki daftar pemain (daftar), itu bukan daftar pemain.

Jika saya menulis kode ini, kelas mungkin akan terlihat seperti ini:

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    // Yes. I used LINQ here. This is so I don't have to worry about
    // _roster.Length vs _roster.Count vs anything else.
    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}
saya
sumber
6
" posting Anda memiliki seluruh pertanyaan detektif " - Yah, saya mengatakan saya benar-benar bingung. Terima kasih telah menghubungkan ke pertanyaan @ Readonly, saya sekarang melihat bahwa sebagian besar dari pertanyaan saya adalah kasus khusus dari masalah Komposisi vs. Warisan yang lebih umum.
Biasa
3
@Brian: Kecuali Anda tidak dapat membuat alias menggunakan generik, semua jenis harus konkret. Dalam contoh saya, List<T>diperluas sebagai kelas dalam pribadi yang menggunakan obat generik untuk menyederhanakan penggunaan dan deklarasi List<T>. Jika ekstensi itu hanya class FootballRoster : List<FootballPlayer> { }, maka ya saya bisa menggunakan alias sebagai gantinya. Saya kira perlu dicatat bahwa Anda dapat menambahkan anggota tambahan / fungsionalitas, tetapi terbatas karena Anda tidak dapat menimpa anggota penting List<T>.
saya
6
Jika ini adalah Programmers.SE, saya akan setuju dengan rentang suara jawaban saat ini, tetapi, karena ini adalah posting SO, jawaban ini setidaknya berupaya untuk menjawab masalah spesifik C # (.NET), meskipun ada definisi umum yang pasti masalah desain.
Mark Hurd
7
Collection<T> allows for members (i.e. Add, Remove, etc.) to be overriddenNah, itu alasan bagus untuk tidak mewarisi dari Daftar. Jawaban yang lain memiliki filosofi yang terlalu banyak.
Navin
10
@my: Saya curiga maksud Anda "membunuh" pertanyaan, karena detektif adalah detektif.
batty
152
class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal;
}

Kode sebelumnya berarti: sekelompok pria dari jalanan bermain sepak bola, dan mereka memiliki nama. Sesuatu seperti:

Orang-orang bermain sepakbola

Bagaimanapun, kode ini (dari jawaban saya)

public class FootballTeam
{
    // A team's name
    public string TeamName; 

    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

Berarti: ini adalah tim sepak bola yang memiliki manajemen, pemain, admin, dll. Sesuatu seperti:

Gambar yang menunjukkan anggota (dan atribut lainnya) dari tim Manchester United

Ini adalah bagaimana logika Anda disajikan dalam gambar ...

Nean Der Thal
sumber
9
Saya berharap bahwa contoh kedua Anda adalah Klub Sepakbola, bukan tim sepak bola. Klub sepak bola memiliki manajer dan admin, dll. Dari sumber ini : "Sebuah tim sepak bola adalah nama kolektif yang diberikan kepada sekelompok pemain ... Tim semacam itu dapat dipilih untuk bermain dalam pertandingan melawan tim lawan, untuk mewakili klub sepak bola ... " Jadi menurut saya, tim adalah daftar pemain (atau mungkin lebih tepatnya kumpulan pemain).
Ben
3
Lebih dioptimalkanprivate readonly List<T> _roster = new List<T>(44);
SiD
2
Penting untuk mengimpor System.Linqnamespace.
Davide Cannizzo
1
Untuk visual: +1
V.7
115

Ini adalah contoh klasik dari komposisi vs warisan .

Dalam kasus khusus ini:

Apakah tim daftar pemain dengan perilaku yang ditambahkan

atau

Apakah tim objek sendiri yang kebetulan berisi daftar pemain.

Dengan memperpanjang Daftar, Anda membatasi diri dengan beberapa cara:

  1. Anda tidak dapat membatasi akses (misalnya, menghentikan orang mengubah daftar). Anda mendapatkan semua metode Daftar apakah Anda membutuhkan / menginginkan semuanya atau tidak.

  2. Apa yang terjadi jika Anda ingin memiliki daftar hal lain juga. Misalnya, tim memiliki pelatih, manajer, penggemar, peralatan, dll. Beberapa dari mereka mungkin daftar dalam hak mereka sendiri.

  3. Anda membatasi opsi Anda untuk warisan. Misalnya Anda mungkin ingin membuat objek Tim generik, dan kemudian memiliki BaseballTeam, FootballTeam, dll. Yang diwarisi dari itu. Untuk mewarisi dari Daftar, Anda perlu melakukan warisan dari Tim, tetapi itu berarti semua jenis tim dipaksa untuk memiliki implementasi daftar yang sama.

Komposisi - termasuk objek yang memberikan perilaku yang Anda inginkan di dalam objek Anda.

Warisan - objek Anda menjadi turunan dari objek yang memiliki perilaku yang Anda inginkan.

Keduanya memiliki kegunaannya, tetapi ini adalah kasus yang jelas di mana komposisi lebih disukai.

Tim B
sumber
1
Memperluas pada # 2, tidak masuk akal untuk Daftar memiliki- Daftar.
Cruncher
Pertama, pertanyaannya tidak ada hubungannya dengan komposisi vs warisan. Jawabannya adalah OP tidak ingin menerapkan jenis daftar yang lebih spesifik, jadi dia tidak perlu memperpanjang Daftar <>. Saya bengkok karena Anda memiliki skor yang sangat tinggi pada stackoverflow dan harus mengetahui hal ini dengan jelas dan orang-orang mempercayai apa yang Anda katakan, jadi saat ini sekurang-kurangnya 55 orang yang diputuskan pendapatnya dan yang idenya bingung percaya baik cara atau yang lain ok untuk membangun sebuah sistem tetapi jelas tidak! # no-rage
Mauro Sampietro
6
@ Sam Dia ingin perilaku daftar. Ia memiliki dua pilihan, ia dapat memperpanjang daftar (warisan) atau ia dapat memasukkan daftar di dalam objeknya (komposisi). Mungkin Anda salah memahami bagian dari pertanyaan atau jawaban, bukannya 55 orang salah dan Anda benar? :)
Tim B
4
Iya. Ini kasus yang merosot dengan hanya satu super dan sub tapi saya memberikan penjelasan yang lebih umum untuk pertanyaan spesifiknya. Dia mungkin hanya memiliki satu tim dan yang mungkin selalu memiliki satu daftar tetapi pilihannya masih mewarisi daftar atau memasukkannya sebagai objek internal. Setelah Anda mulai memasukkan beberapa jenis tim (sepak bola. Kriket. Dll) dan mereka mulai menjadi lebih dari sekadar daftar pemain, Anda melihat bagaimana Anda memiliki komposisi lengkap vs. pertanyaan pewarisan. Melihat gambaran besar itu penting dalam kasus-kasus seperti ini untuk menghindari pilihan yang salah sejak dini yang kemudian berarti banyak refactoring nanti.
Tim B
3
OP tidak meminta Komposisi, tetapi use case adalah contoh jelas dari masalah X: Y yang salah satu dari 4 prinsip pemrograman berorientasi objek rusak, disalahgunakan, atau tidak sepenuhnya dipahami. Jawaban yang lebih jelas adalah menulis koleksi khusus seperti Stack atau Queue (yang tampaknya tidak cocok dengan use case) atau untuk memahami komposisi. Tim Sepakbola BUKAN Daftar Pemain sepakbola. Apakah OP memintanya secara eksplisit atau tidak tidak relevan, tanpa memahami Abstraksi, Enkapsulasi, Warisan, dan Polimorfisme, mereka tidak akan mengerti jawabannya, ergo, X: Y amok
Julia McGuigan
67

Seperti yang semua orang tunjukkan, tim pemain bukanlah daftar pemain. Kesalahan ini dibuat oleh banyak orang di mana saja, mungkin pada berbagai tingkat keahlian. Seringkali masalahnya halus dan kadang-kadang sangat kotor, seperti dalam kasus ini. Desain seperti itu buruk karena melanggar Prinsip Pergantian Liskov . Internet memiliki banyak artikel bagus yang menjelaskan konsep ini misalnya, http://en.wikipedia.org/wiki/Liskov_substitution_principle

Singkatnya, ada dua aturan yang harus dilestarikan dalam hubungan Orangtua / Anak di antara kelas:

  • Seorang anak seharusnya tidak memerlukan karakteristik yang kurang dari apa yang sepenuhnya mendefinisikan Orang Tua.
  • Orangtua seharusnya tidak memerlukan karakteristik selain apa yang sepenuhnya mendefinisikan Anak.

Dengan kata lain, Orangtua adalah definisi yang diperlukan dari seorang anak, dan seorang anak adalah definisi yang cukup dari Orangtua.

Berikut adalah cara untuk memikirkan solusi yang ada dan menerapkan prinsip di atas yang seharusnya membantu seseorang menghindari kesalahan seperti itu. Seseorang harus menguji hipotesisnya dengan memverifikasi jika semua operasi kelas induk valid untuk kelas turunan baik secara struktural maupun semantik.

  • Apakah tim sepak bola adalah daftar pemain sepak bola? (Apakah semua properti dari daftar berlaku untuk tim dalam arti yang sama)
    • Apakah tim merupakan kumpulan entitas yang homogen? Ya, tim adalah koleksi Pemain
    • Apakah urutan penyertaan pemain deskriptif tentang kondisi tim dan apakah tim memastikan bahwa urutannya dipertahankan kecuali diubah secara eksplisit? Tidak, dan Tidak
    • Apakah pemain diharapkan untuk dimasukkan / dijatuhkan berdasarkan posisi sekuensial mereka dalam tim? Tidak

Seperti yang Anda lihat, hanya karakteristik pertama dari daftar yang berlaku untuk tim. Karenanya tim bukan daftar. Daftar akan menjadi detail implementasi tentang bagaimana Anda mengelola tim Anda, sehingga hanya digunakan untuk menyimpan objek pemain dan dimanipulasi dengan metode kelas Tim.

Pada titik ini saya ingin berkomentar bahwa kelas Tim seharusnya, menurut pendapat saya, bahkan tidak diimplementasikan menggunakan Daftar; itu harus diimplementasikan menggunakan struktur data Set (HashSet, misalnya) dalam banyak kasus.

Satyan Raina
sumber
8
Hasil tangkapan bagus pada Daftar versus Perangkat. Tampaknya itu hanya kesalahan yang biasa dilakukan. Ketika hanya ada satu instance elemen dalam koleksi, semacam set, atau kamus harus menjadi kandidat yang disukai untuk implementasi. Tidak masalah memiliki tim dengan dua pemain dengan nama yang sama. Tidak apa-apa untuk memasukkan satu pemain dua kali sekaligus.
Fred Mitchell
1
Memberi +1 untuk beberapa poin bagus, meskipun lebih penting untuk bertanya "dapatkah struktur data secara wajar mendukung segala sesuatu yang bermanfaat (idealnya jangka pendek dan jangka panjang)", daripada "apakah struktur data melakukan lebih daripada berguna".
Tony Delroy
1
@ TonyD Yah, poin yang saya angkat bukanlah bahwa orang harus memeriksa "jika struktur data melakukan lebih dari apa yang berguna". Ini untuk memeriksa "Jika struktur data Induk melakukan sesuatu yang tidak relevan, tidak berarti atau kontra-intuitif dengan perilaku yang mungkin ditimbulkan oleh kelas Anak".
Satyan Raina
1
@ TonyD Sebenarnya ada masalah dengan memiliki karakteristik yang tidak relevan yang berasal dari Orang Tua karena akan gagal dalam tes negatif dalam banyak kasus. Seorang programmer dapat memperluas Human {eat (); Lari(); tulis ();} dari Gorila {eat (); Lari(); swing ();} berpikir tidak ada yang salah dengan manusia dengan fitur tambahan untuk dapat diayunkan. Dan kemudian di dunia game, manusia Anda tiba-tiba mulai memotong semua rintangan darat yang seharusnya dengan hanya berayun di atas pohon. Kecuali ditentukan secara eksplisit, Manusia yang praktis seharusnya tidak dapat berayun. Desain seperti itu membuat api sangat terbuka untuk disalahgunakan dan membingungkan
Satyan Raina
1
@ TonyD Saya tidak menyarankan agar kelas Player juga diturunkan dari HashSet. Saya menyarankan kelas Player harus 'dalam banyak kasus' diimplementasikan menggunakan HashSet via Komposisi dan itu benar-benar detail level implementasi, bukan level desain (itulah sebabnya saya menyebutkannya sebagai catatan jawaban saya). Ini bisa sangat baik diimplementasikan menggunakan daftar jika ada pembenaran yang valid untuk implementasi semacam itu. Jadi untuk menjawab pertanyaan Anda, Apakah perlu memiliki O (1) pencarian dengan kunci? Tidak. Karena itu seseorang TIDAK boleh memperpanjang Player dari HashSet juga.
Satyan Raina
50

Bagaimana jika FootballTeammemiliki tim cadangan bersama dengan tim utama?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
}

Bagaimana Anda memodelkan itu dengan?

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

Hubungannya jelas memiliki dan tidak adalah .

atau RetiredPlayers?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
    List<FootballPlayer> RetiredPlayers { get; set; }
}

Sebagai aturan praktis, jika Anda ingin mewarisi dari koleksi, beri nama kelas SomethingCollection.

Apakah secara SomethingCollectionsemantik Anda masuk akal? Hanya lakukan ini jika jenis Anda adalah koleksi Something.

Dalam hal FootballTeamitu tidak terdengar benar. A Teamlebih dari a Collection. A Teamdapat memiliki pelatih, pelatih, dll seperti yang ditunjukkan oleh jawaban lainnya.

FootballCollectionterdengar seperti kumpulan bola kaki atau mungkin koleksi perlengkapan sepak bola. TeamCollection, kumpulan tim.

FootballPlayerCollectionterdengar seperti kumpulan pemain yang akan menjadi nama yang valid untuk kelas yang mewarisi dari List<FootballPlayer>jika Anda benar-benar ingin melakukan itu.

Benar List<FootballPlayer>-benar tipe yang sangat baik untuk dihadapi. Mungkin IList<FootballPlayer>jika Anda mengembalikannya dari suatu metode.

Singkatnya

Bertanya pada diri sendiri

  1. Apakah X a Y? atau Memiliki X sebuah Y?

  2. Apakah nama kelas saya berarti apa adanya?

Sam Leach
sumber
Jika jenis setiap pemain akan mengklasifikasikan sebagai milik baik DefensivePlayers, OffensivePlayersatau OtherPlayers, mungkin sah berguna untuk memiliki jenis yang dapat digunakan oleh kode yang mengharapkan List<Player>tetapi anggota juga termasuk DefensivePlayers, OffsensivePlayersatau SpecialPlayerstipe IList<DefensivePlayer>, IList<OffensivePlayer>dan IList<Player>. Orang dapat menggunakan objek terpisah untuk menembolok daftar yang terpisah, tetapi merangkumnya dalam objek yang sama dengan daftar utama akan tampak lebih bersih [gunakan pembatalan daftar pencacah ...
supercat
... sebagai isyarat untuk fakta bahwa daftar utama telah berubah dan sub-daftar harus dibuat ulang ketika mereka selanjutnya diakses].
supercat
2
Sementara saya setuju dengan poin yang dibuat, itu benar-benar merobek jiwa saya untuk melihat seseorang memberikan saran desain dan menyarankan mengekspos Daftar konkret <T> dengan pengambil dan penyetel publik dalam objek bisnis :(
sara
38

Desain> Implementasi

Metode dan properti apa yang Anda paparkan adalah keputusan desain. Apa kelas dasar yang Anda warisi adalah detail implementasi. Saya merasa perlu mengambil langkah mundur ke yang pertama.

Objek adalah kumpulan data dan perilaku.

Jadi pertanyaan pertama Anda seharusnya:

  • Data apa yang terdiri dari objek ini dalam model yang saya buat?
  • Perilaku apa yang ditunjukkan benda ini dalam model itu?
  • Bagaimana ini bisa berubah di masa depan?

Ingatlah bahwa pewarisan menyiratkan hubungan "isa" (adalah), sedangkan komposisi menyiratkan hubungan "memiliki" (hasa). Pilih yang tepat untuk situasi Anda dalam pandangan Anda, mengingat ke mana hal-hal mungkin terjadi ketika aplikasi Anda berkembang.

Pertimbangkan berpikir dalam antarmuka sebelum Anda berpikir dalam tipe konkret, karena beberapa orang merasa lebih mudah untuk menempatkan otak mereka dalam "mode desain" seperti itu.

Ini bukan sesuatu yang dilakukan setiap orang secara sadar pada level ini dalam koding sehari-hari. Tetapi jika Anda merenungkan topik semacam ini, Anda melangkah di perairan desain. Menyadari hal itu bisa membebaskan.

Pertimbangkan Spesifikasi Desain

Lihatlah Daftar <T> dan IList <T> di MSDN atau Visual Studio. Lihat metode dan properti apa yang mereka tampilkan. Apakah semua metode ini terlihat seperti sesuatu yang seseorang ingin lakukan terhadap FootballTeam dalam pandangan Anda?

Apakah footballTeam.Reverse () masuk akal bagi Anda? Apakah footballTeam.ConvertAll <TOutput> () terlihat seperti sesuatu yang Anda inginkan?

Ini bukan pertanyaan jebakan; jawabannya mungkin benar-benar "ya". Jika Anda menerapkan / mewarisi Daftar <Player> atau IList <Player>, Anda terjebak dengannya; jika itu ideal untuk model Anda, lakukanlah.

Jika Anda memutuskan ya, itu masuk akal, dan Anda ingin objek Anda dapat diperlakukan sebagai kumpulan / daftar pemain (perilaku), dan karena itu Anda ingin menerapkan ICollection atau IList, dengan segala cara melakukannya. Secara alami:

class FootballTeam : ... ICollection<Player>
{
    ...
}

Jika Anda ingin objek Anda berisi koleksi / daftar pemain (data), dan karena itu Anda ingin koleksi atau daftar menjadi properti atau anggota, dengan segala cara melakukannya. Secara alami:

class FootballTeam ...
{
    public ICollection<Player> Players { get { ... } }
}

Anda mungkin merasa bahwa Anda ingin orang hanya dapat menyebutkan satu set pemain, daripada menghitungnya, menambah atau menghapusnya. IEnumerable <Player> adalah opsi yang benar-benar valid untuk dipertimbangkan.

Anda mungkin merasa bahwa tidak ada antarmuka ini yang berguna dalam model Anda sama sekali. Ini lebih kecil kemungkinannya (IEnumerable <T> berguna dalam banyak situasi) tetapi masih mungkin.

Siapa pun yang mencoba memberi tahu Anda bahwa salah satu di antaranya salah dan pasti salah dalam setiap kasus adalah salah arah. Siapa pun yang mencoba memberi tahu Anda secara kategoris dan definitif benar dalam setiap kasus adalah kesalahan.

Pindah ke Implementasi

Setelah Anda memutuskan data dan perilaku, Anda dapat membuat keputusan tentang implementasi. Ini termasuk kelas konkret yang Anda andalkan melalui pewarisan atau komposisi.

Ini mungkin bukan langkah besar, dan orang sering mengacaukan desain dan implementasi karena sangat mungkin untuk menjalankan semuanya di kepala Anda dalam satu atau dua detik dan mulai mengetik.

Eksperimen Pikiran

Contoh buatan: seperti yang orang lain katakan, sebuah tim tidak selalu "hanya" kumpulan pemain. Apakah Anda memelihara koleksi skor pertandingan untuk tim? Apakah tim dapat dipertukarkan dengan klub, sesuai model Anda? Jika demikian, dan jika tim Anda adalah kumpulan pemain, mungkin itu juga kumpulan staf dan / atau kumpulan skor. Kemudian Anda berakhir dengan:

class FootballTeam : ... ICollection<Player>, 
                         ICollection<StaffMember>,
                         ICollection<Score>
{
    ....
}

Meskipun desain, pada titik ini di C # Anda tidak akan dapat menerapkan semua ini dengan mewarisi dari Daftar <T>, karena C # "hanya" mendukung pewarisan tunggal. (Jika Anda sudah mencoba ini di C ++, Anda mungkin menganggap ini sebagai Good Thing.) Menerapkan satu koleksi melalui warisan dan satu melalui komposisi cenderung terasa kotor. Dan properti seperti Count menjadi membingungkan bagi pengguna kecuali Anda menerapkan ILIst <Player> .Count dan IList <StaffMember> .Count dll. Secara eksplisit, dan kemudian mereka hanya menyakitkan daripada membingungkan. Anda bisa melihat ke mana arahnya; tetapi perasaan saat memikirkan jalan ini mungkin memberi tahu Anda merasa salah untuk menuju ke arah ini (dan benar atau salah, kolega Anda mungkin juga jika Anda menerapkannya dengan cara ini!)

Jawaban Singkat (Terlambat)

Pedoman tentang tidak mewarisi dari kelas koleksi tidak spesifik C #, Anda akan menemukannya dalam banyak bahasa pemrograman. Itu diterima kebijaksanaan bukan hukum. Salah satu alasannya adalah bahwa dalam praktiknya komposisi dianggap sering menang atas warisan dalam hal kelengkapan, penerapan, dan pemeliharaan. Ini lebih umum dengan objek dunia / domain nyata untuk menemukan hubungan "hasa" yang berguna dan konsisten daripada hubungan "isa" yang berguna dan konsisten kecuali Anda tenggelam dalam abstrak, terutama saat waktu berlalu dan data serta perilaku yang tepat dari objek dalam kode perubahan. Ini seharusnya tidak menyebabkan Anda selalu mengesampingkan pewarisan dari kelas koleksi; tapi mungkin sugestif.

El Zorko
sumber
34

Pertama-tama, itu berkaitan dengan kegunaan. Jika Anda menggunakan warisan, Teamkelas akan mengekspos perilaku (metode) yang dirancang murni untuk manipulasi objek. Misalnya, AsReadOnly()atau CopyTo(obj)metode tidak masuk akal untuk objek tim. Alih-alih AddRange(items)metode yang Anda mungkin ingin metode yang lebih deskriptif AddPlayers(players).

Jika Anda ingin menggunakan LINQ, mengimplementasikan antarmuka umum seperti ICollection<T>atau IEnumerable<T>akan lebih masuk akal.

Seperti disebutkan, komposisi adalah cara yang tepat untuk melakukannya. Cukup terapkan daftar pemain sebagai variabel pribadi.

Dmitry S.
sumber
30

Tim sepak bola bukan daftar pemain sepak bola. Tim sepak bola terdiri dari daftar pemain sepak bola!

Ini secara logis salah:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

dan ini benar:

class FootballTeam 
{ 
    public List<FootballPlayer> players
    public string TeamName; 
    public int RunningTotal 
}
Mauro Sampietro
sumber
19
Ini tidak menjelaskan mengapa . OP tahu bahwa sebagian besar programmer lain merasakan hal ini, ia hanya tidak mengerti mengapa itu penting. Ini hanya benar-benar memberikan informasi yang sudah ada dalam pertanyaan.
Servy
2
Ini adalah hal pertama yang saya pikirkan juga, bagaimana data yang dimodelkannya tidak tepat. Jadi alasannya mengapa, model data Anda salah.
rball
3
Mengapa tidak mewarisi dari daftar? Karena dia tidak ingin daftar jenis yang lebih spesifik.
Mauro Sampietro
2
Saya tidak berpikir contoh kedua benar. Anda mengekspos keadaan dan dengan demikian melanggar enkapsulasi. Negara harus disembunyikan / internal ke objek dan cara untuk bermutasi negara ini harus melalui metode publik. Metode mana yang tepat adalah sesuatu yang harus ditentukan dari persyaratan bisnis, tetapi harus pada "butuh ini sekarang" -basis, bukan "mungkin berguna beberapa kali saya tidak tahu" -basis. Jaga agar api Anda tetap kencang dan Anda akan menghemat waktu dan tenaga Anda.
sara
1
Enkapsulasi bukanlah inti dari pertanyaan. Saya fokus untuk tidak memperluas jenis jika Anda tidak ingin jenis itu berperilaku dengan cara yang lebih spesifik. Bidang-bidang tersebut harus merupakan properti otomatis dalam c # dan mendapatkan / menetapkan metode dalam bahasa lain, tetapi di sini dalam konteks ini yang benar-benar berlebihan
Mauro Sampietro
28

Itu tergantung pada konteksnya

Ketika Anda mempertimbangkan tim Anda sebagai daftar pemain, Anda memproyeksikan "ide" tim bola kaki ke satu aspek: Anda mengurangi "tim" menjadi orang-orang yang Anda lihat di lapangan. Proyeksi ini hanya benar dalam konteks tertentu. Dalam konteks yang berbeda, ini mungkin sepenuhnya salah. Bayangkan Anda ingin menjadi sponsor tim. Jadi, Anda harus berbicara dengan manajer tim. Dalam konteks ini tim diproyeksikan ke daftar manajernya. Dan kedua daftar ini biasanya tidak tumpang tindih. Konteks lain adalah saat ini versus mantan pemain, dll.

Semantik yang tidak jelas

Jadi masalah dengan mempertimbangkan tim sebagai daftar pemainnya adalah semantiknya tergantung pada konteksnya dan bahwa itu tidak dapat diperpanjang ketika konteksnya berubah. Selain itu sulit untuk mengekspresikan, konteks mana yang Anda gunakan.

Kelas bisa diperluas

Ketika Anda menggunakan kelas dengan hanya satu anggota (misalnya IList activePlayers), Anda dapat menggunakan nama anggota (dan juga komentarnya) untuk membuat konteksnya lebih jelas. Ketika ada konteks tambahan, Anda hanya menambahkan anggota tambahan.

Kelas lebih kompleks

Dalam beberapa kasus mungkin terlalu sulit untuk membuat kelas tambahan. Setiap definisi kelas harus dimuat melalui classloader dan akan di-cache oleh mesin virtual. Ini akan membuat Anda kehilangan kinerja dan memori. Ketika Anda memiliki konteks yang sangat spesifik, mungkin boleh saja mempertimbangkan tim sepak bola sebagai daftar pemain. Tetapi dalam kasus ini, Anda harus benar-benar hanya menggunakan a IList , bukan kelas yang berasal darinya.

Kesimpulan / Pertimbangan

Ketika Anda memiliki konteks yang sangat spesifik, tidak masalah untuk mempertimbangkan tim sebagai daftar pemain. Sebagai contoh di dalam metode itu sepenuhnya OK untuk menulis:

IList<Player> footballTeam = ...

Saat menggunakan F #, bahkan bisa OK untuk membuat singkatan jenis:

type FootballTeam = IList<Player>

Tetapi ketika konteksnya lebih luas atau bahkan tidak jelas, Anda seharusnya tidak melakukan ini. Ini terutama terjadi ketika Anda membuat kelas baru yang konteksnya di mana ia dapat digunakan di masa depan tidak jelas. Tanda peringatan adalah ketika Anda mulai menambahkan atribut tambahan ke kelas Anda (nama tim, pelatih, dll.). Ini adalah tanda yang jelas bahwa konteks di mana kelas akan digunakan tidak tetap dan akan berubah di masa depan. Dalam hal ini Anda tidak dapat menganggap tim sebagai daftar pemain, tetapi Anda harus membuat model daftar pemain (saat ini aktif, tidak cedera, dll.) Sebagai atribut tim.

stefan.schwetschke
sumber
27

Biarkan saya menulis ulang pertanyaan Anda. sehingga Anda dapat melihat subjek dari perspektif yang berbeda.

Ketika saya perlu mewakili tim sepak bola, saya mengerti bahwa itu pada dasarnya adalah sebuah nama. Seperti: "Eagles"

string team = new string();

Kemudian saya menyadari tim juga memiliki pemain.

Mengapa saya tidak bisa hanya memperpanjang jenis string sehingga juga menyimpan daftar pemain?

Titik masuk Anda ke masalah ini sewenang-wenang. Cobalah untuk berpikir apa yang tim memiliki (sifat), bukan apa yang .

Setelah Anda melakukannya, Anda bisa melihat apakah itu berbagi properti dengan kelas lain. Dan pikirkan tentang warisan.

pengguna1852503
sumber
1
Itu poin yang bagus - orang bisa menganggap tim hanya sebagai nama. Namun, jika aplikasi saya bertujuan untuk bekerja dengan aksi para pemain, maka pemikiran itu agak kurang jelas. Pokoknya, pada akhirnya masalah ini mengarah pada komposisi vs. pewarisan.
Biasa
6
Itu adalah salah satu cara berjasa untuk melihatnya. Sebagai catatan, tolong pertimbangkan ini: Seorang pria kaya memiliki beberapa tim sepak bola, ia memberikan satu kepada seorang teman sebagai hadiah, teman itu mengubah nama tim, memecat pelatih, dan mengganti semua pemain. Tim teman bertemu dengan tim putra di green dan saat tim putra kalah, dia berkata, "Aku tidak percaya kau mengalahkanku dengan tim yang kuberikan padamu!" apakah pria itu benar? bagaimana Anda memeriksa ini?
user1852503
20

Hanya karena saya pikir jawaban lain cukup banyak bersinggungan dengan apakah tim sepak bola "is-a" List<FootballPlayer>atau "has-a" List<FootballPlayer>, yang benar-benar tidak menjawab pertanyaan ini sebagaimana tertulis.

OP terutama meminta klarifikasi tentang pedoman untuk mewarisi dari List<T>:

Pedoman mengatakan bahwa Anda tidak boleh mewarisi dari List<T>. Kenapa tidak?

Karena List<T>tidak memiliki metode virtual. Ini tidak terlalu menjadi masalah dalam kode Anda sendiri, karena Anda biasanya dapat menonaktifkan implementasinya dengan sedikit rasa sakit - tetapi bisa menjadi masalah yang jauh lebih besar di API publik.

Apa itu API publik dan mengapa saya harus peduli?

API publik adalah antarmuka yang Anda tampilkan ke pemrogram pihak ketiga. Pikirkan kode kerangka kerja. Dan ingat bahwa pedoman yang direferensikan adalah ".NET Framework Design Guidelines" dan bukan ".NET Application Design Guidelines". Ada perbedaan, dan - secara umum - desain API publik jauh lebih ketat.

Jika proyek saya saat ini tidak dan sepertinya tidak pernah memiliki API publik ini, dapatkah saya dengan aman mengabaikan panduan ini? Jika saya mewarisi dari Daftar dan ternyata saya memerlukan API publik, kesulitan apa yang akan saya miliki?

Cukup banyak, ya. Anda mungkin ingin mempertimbangkan alasan di baliknya untuk melihat apakah itu berlaku untuk situasi Anda, tetapi jika Anda tidak membangun API publik maka Anda tidak perlu terlalu khawatir tentang masalah API seperti versi (yang, ini adalah bagian).

Jika Anda menambahkan API publik di masa mendatang, Anda perlu mengambil abstrak API Anda dari implementasi Anda (dengan tidak mengekspos Anda List<T>secara langsung) atau melanggar pedoman dengan kemungkinan rasa sakit di masa depan yang mungkin timbul.

Mengapa itu penting? Daftar adalah daftar. Apa yang mungkin bisa berubah? Apa yang mungkin ingin saya ubah?

Bergantung pada konteksnya, tetapi karena kita menggunakan FootballTeamsebagai contoh - bayangkan Anda tidak dapat menambahkan FootballPlayerjika itu akan menyebabkan tim melewati batasan gaji. Cara yang mungkin untuk menambahkannya adalah seperti:

 class FootballTeam : List<FootballPlayer> {
     override void Add(FootballPlayer player) {
        if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
 }

Ah ... tetapi Anda tidak bisa override Addkarena itu bukan virtual(untuk alasan kinerja).

Jika Anda berada dalam suatu aplikasi (yang, pada dasarnya, berarti bahwa Anda dan semua penelepon Anda dikompilasi bersama) maka Anda sekarang dapat mengubah untuk menggunakan IList<T>dan memperbaiki segala kesalahan kompilasi:

 class FootballTeam : IList<FootballPlayer> {
     private List<FootballPlayer> Players { get; set; }

     override void Add(FootballPlayer player) {
        if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
     /* boiler plate for rest of IList */
 }

tetapi, jika Anda secara terbuka terpapar ke pihak ke-3 Anda baru saja membuat perubahan melanggar yang akan menyebabkan kesalahan kompilasi dan / atau runtime.

TL; DR - pedomannya adalah untuk API publik. Untuk API pribadi, lakukan apa yang Anda inginkan.

Mark Brackett
sumber
18

Apakah memungkinkan orang untuk mengatakan

myTeam.subList(3, 5);

masuk akal sama sekali? Jika tidak maka itu seharusnya bukan Daftar.

Cruncher
sumber
3
Mungkin jika Anda menyebutnyamyTeam.subTeam(3, 5);
Sam Leach
3
@ SamLeach Dengan anggapan itu benar, maka Anda masih akan membutuhkan komposisi bukan warisan. Karena subListbahkan tidak akan mengembalikan Teamlagi.
Cruncher
1
Ini meyakinkan saya lebih dari jawaban lain. +1
FireCubez
16

Itu tergantung pada perilaku objek "tim" Anda. Jika berperilaku seperti koleksi, mungkin OK untuk mewakilinya terlebih dahulu dengan Daftar biasa. Kemudian Anda mungkin mulai memperhatikan bahwa Anda terus menggandakan kode yang tertera pada daftar; pada titik ini Anda memiliki opsi untuk membuat objek FootballTeam yang membungkus daftar pemain. Kelas FootballTeam menjadi rumah bagi semua kode yang tertera di daftar pemain.

Itu membuat kode saya verbose tidak perlu. Sekarang saya harus memanggil my_team.Players.Count, bukan hanya my_team.Count. Untungnya, dengan C # saya dapat mendefinisikan pengindeks untuk membuat pengindeksan transparan, dan meneruskan semua metode Daftar internal ... Tapi itu banyak kode! Apa yang saya dapat untuk semua pekerjaan itu?

Enkapsulasi. Klien Anda tidak perlu tahu apa yang terjadi di dalam FootballTeam. Untuk semua klien Anda tahu, itu mungkin dilaksanakan dengan melihat daftar pemain di basis data. Mereka tidak perlu tahu, dan ini meningkatkan desain Anda.

Itu tidak masuk akal. Tim sepak bola tidak "memiliki" daftar pemain. Ini adalah daftar pemain. Anda tidak mengatakan "John McFootballer telah bergabung dengan pemain SomeTeam". Anda mengatakan "John telah bergabung dengan SomeTeam". Anda tidak menambahkan huruf ke "karakter string", Anda menambahkan huruf ke string. Anda tidak menambahkan buku ke buku perpustakaan, Anda menambahkan buku ke perpustakaan.

Tepat :) Anda akan mengatakan footballTeam.Add (john), bukan footballTeam.List.Add (john). Daftar internal tidak akan terlihat.

xpmatteo
sumber
1
OP ingin memahami bagaimana MODEL REALITY tetapi kemudian mendefinisikan tim sepak bola sebagai daftar pemain sepak bola (yang salah secara konsep). Ini masalahnya. Argumen lain yang menyesatkan dia menurut saya.
Mauro Sampietro
3
Saya tidak setuju bahwa daftar pemain secara konsep salah. Belum tentu. Seperti yang ditulis orang lain di halaman ini, "Semua model salah, tetapi ada yang berguna"
xpmatteo
Model yang tepat sulit didapat, setelah selesai mereka adalah model yang berguna. Jika sebuah model dapat disalahgunakan, itu salah secara konsep. stackoverflow.com/a/21706411/711061
Mauro Sampietro
15

Ada banyak jawaban bagus di sini, tetapi saya ingin menyentuh sesuatu yang tidak saya lihat disebutkan: Desain berorientasi objek adalah tentang memberdayakan objek .

Anda ingin merangkum semua aturan Anda, pekerjaan tambahan dan detail internal di dalam objek yang sesuai. Dengan cara ini objek lain yang berinteraksi dengan yang ini tidak perlu khawatir tentang itu semua. Bahkan, Anda ingin melangkah lebih jauh dan secara aktif mencegah objek lain melewati bagian dalam ini.

Ketika Anda mewarisi dari List, semua objek lain dapat melihat Anda sebagai Daftar. Mereka memiliki akses langsung ke metode untuk menambah dan menghapus pemain. Dan Anda akan kehilangan kendali; sebagai contoh:

Misalkan Anda ingin membedakan ketika seorang pemain pergi dengan mengetahui apakah mereka pensiun, mengundurkan diri, atau dipecat. Anda bisa menerapkan RemovePlayermetode yang membutuhkan input enum yang sesuai. Namun, dengan mewarisi dari List, Anda tidak akan dapat mencegah akses langsung ke Remove, RemoveAlldan bahkan Clear. Akibatnya, Anda benar-benar melemahkanFootballTeam kelas Anda .


Pikiran tambahan tentang enkapsulasi ... Anda mengemukakan kekhawatiran berikut:

Itu membuat kode saya verbose tidak perlu. Sekarang saya harus memanggil my_team.Players.Count, bukan hanya my_team.Count.

Anda benar, itu akan menjadi kata yang tidak perlu bagi semua klien untuk menggunakan tim Anda. Namun, masalah itu sangat kecil jika dibandingkan dengan fakta bahwa Anda telah terpapar List Playerspada semuanya sehingga mereka dapat bermain-main dengan tim Anda tanpa persetujuan Anda.

Anda selanjutnya berkata:

Itu tidak masuk akal. Tim sepak bola tidak "memiliki" daftar pemain. Ini adalah daftar pemain. Anda tidak mengatakan "John McFootballer telah bergabung dengan pemain SomeTeam". Anda mengatakan "John telah bergabung dengan SomeTeam".

Anda salah tentang bit pertama: Jatuhkan kata 'daftar', dan sebenarnya jelas bahwa sebuah tim memiliki pemain.
Namun, Anda memukul kuku di kepala dengan yang kedua. Anda tidak ingin klien menelepon ateam.Players.Add(...). Anda ingin mereka memanggil ateam.AddPlayer(...). Dan implementasi Anda akan (mungkin antara lain) memanggil secara Players.Add(...)internal.


Semoga Anda bisa melihat betapa pentingnya enkapsulasi terhadap tujuan memberdayakan objek Anda. Anda ingin memungkinkan setiap kelas untuk melakukan tugasnya dengan baik tanpa takut gangguan dari objek lain.

Kecewa
sumber
12

Apa cara C # yang benar untuk merepresentasikan struktur data ...

Ingat, "Semua model salah, tetapi beberapa berguna." - George EP Box

Tidak ada "cara yang benar", hanya yang bermanfaat.

Pilih satu yang bermanfaat bagi Anda dan / pengguna Anda. Itu dia. Berkembang secara ekonomi, jangan over-engineer. Semakin sedikit kode yang Anda tulis, semakin sedikit kode yang perlu Anda debug. (baca edisi berikut).

- Diedit

Jawaban terbaik saya adalah ... itu tergantung. Mewarisi dari Daftar akan mengekspos klien kelas ini dengan metode yang mungkin tidak boleh diungkapkan, terutama karena FootballTeam terlihat seperti entitas bisnis.

- Edisi 2

Saya dengan tulus tidak ingat dengan apa yang saya maksudkan pada komentar "jangan over-engineer". Sementara saya percaya bahwa pola pikir KISS adalah panduan yang baik, saya ingin menekankan bahwa mewarisi kelas bisnis dari Daftar akan menciptakan lebih banyak masalah daripada yang dipecahkan, karena kebocoran abstraksi .

Di sisi lain, saya percaya ada sejumlah kasus di mana hanya untuk mewarisi dari Daftar berguna. Seperti yang saya tulis di edisi sebelumnya, itu tergantung. Jawaban untuk setiap kasus sangat dipengaruhi oleh pengetahuan, pengalaman dan preferensi pribadi.

Terima kasih kepada @ kai karena telah membantu saya berpikir lebih tepat tentang jawabannya.

ArturoTena
sumber
Jawaban terbaik saya adalah ... itu tergantung. Mewarisi dari Daftar akan mengekspos klien kelas ini dengan metode yang mungkin tidak boleh diungkapkan, terutama karena FootballTeam terlihat seperti entitas bisnis. Saya tidak tahu apakah saya harus mengedit jawaban ini atau memposting yang lain, ada ide?
ArturoTena
2
Sedikit tentang " mewarisi dari Listakan mengekspos klien kelas ini ke metode yang mungkin tidak boleh diekspos " adalah persis apa yang membuat mewarisi dari Listmutlak tidak-tidak. Setelah terekspos, mereka secara bertahap disalahgunakan dan salah digunakan seiring waktu. Menghemat waktu sekarang dengan "berkembang secara ekonomi" dapat dengan mudah menyebabkan sepuluh kali lipat penghematan yang hilang di masa depan: men-debug pelanggaran dan akhirnya refactoring untuk memperbaiki warisan. Prinsip YAGNI juga dapat dianggap sebagai makna: Anda Tidak Akan Perlu semua metode dari List, jadi jangan memaparkannya.
Kecewa
3
"jangan over-engineer. Semakin sedikit kode yang Anda tulis, semakin sedikit kode yang perlu Anda debug." <- Aku pikir ini menyesatkan. Enkapsulasi dan komposisi lebih dari warisan BUKAN rekayasa berlebihan dan tidak menyebabkan lebih banyak kebutuhan untuk debugging. Dengan merangkum Anda MEMBATASI jumlah cara klien dapat menggunakan (dan menyalahgunakan) kelas Anda dan dengan demikian Anda memiliki lebih sedikit titik masuk yang perlu pengujian dan validasi input. Mewarisi dari Listkarena cepat dan mudah dan dengan demikian akan menyebabkan lebih sedikit bug jelas salah, itu hanya desain yang buruk dan desain yang buruk menyebabkan lebih banyak bug daripada "over engineering".
sara
@ai saya setuju dengan Anda di setiap titik. Saya dengan tulus tidak ingat dengan apa yang saya maksudkan pada komentar "jangan over-engineer". OTOH, saya percaya ada sejumlah kasus di mana hanya untuk mewarisi dari Daftar berguna. Seperti yang saya tulis di edisi selanjutnya, itu tergantung. Jawaban untuk setiap kasus sangat dipengaruhi oleh pengetahuan, pengalaman dan preferensi pribadi. Seperti segala sesuatu dalam hidup. ;-)
ArturoTena
11

Ini mengingatkan saya pada tradeoff "Is a" versus "have". Terkadang lebih mudah dan lebih masuk akal untuk mewarisi langsung dari kelas super. Lain kali lebih masuk akal untuk membuat kelas mandiri dan menyertakan kelas yang akan Anda warisi sebagai variabel anggota. Anda masih dapat mengakses fungsionalitas kelas tetapi tidak terikat dengan antarmuka atau kendala lain yang mungkin berasal dari mewarisi dari kelas.

Yang kamu lakukan Seperti banyak hal ... itu tergantung pada konteksnya. Panduan yang akan saya gunakan adalah bahwa untuk mewarisi dari kelas lain benar-benar harus ada hubungan "adalah". Jadi jika Anda menulis kelas yang disebut BMW, itu bisa mewarisi dari Mobil karena BMW benar-benar mobil. Kelas Kuda dapat mewarisi dari kelas Mamalia karena kuda sebenarnya adalah mamalia dalam kehidupan nyata dan fungsi Mamalia apa pun harus relevan dengan Kuda. Tetapi dapatkah Anda mengatakan bahwa tim adalah daftar? Dari apa yang bisa saya katakan, sepertinya bukan Tim yang benar-benar "adalah" Daftar. Jadi dalam hal ini, saya akan memiliki Daftar sebagai variabel anggota.

Paul J Abernathy
sumber
9

Rahasia kotor saya: Saya tidak peduli apa yang orang katakan, dan saya melakukannya. .NET Framework disebarkan dengan "XxxxCollection" (UIElementCollection untuk contoh teratas di kepala saya).

Jadi apa yang menghentikan saya berkata:

team.Players.ByName("Nicolas")

Ketika saya menemukannya lebih baik daripada

team.ByName("Nicolas")

Selain itu, PlayerCollection saya dapat digunakan oleh kelas lain, seperti "Klub" tanpa duplikasi kode.

club.Players.ByName("Nicolas")

Praktik terbaik kemarin, mungkin bukan yang akan datang. Tidak ada alasan di balik praktik terbaik, sebagian besar hanya kesepakatan luas di antara masyarakat. Alih-alih bertanya kepada komunitas apakah itu akan menyalahkan Anda ketika Anda bertanya pada diri sendiri, apa yang lebih mudah dibaca dan dikelola?

team.Players.ByName("Nicolas") 

atau

team.ByName("Nicolas")

Betulkah. Apakah Anda ragu? Sekarang mungkin Anda harus bermain dengan kendala teknis lainnya yang mencegah Anda menggunakan Daftar <T> dalam kasus penggunaan nyata Anda. Tapi jangan tambahkan kendala yang seharusnya tidak ada. Jika Microsoft tidak mendokumentasikan alasannya, maka itu pasti merupakan "praktik terbaik" yang datang entah dari mana.

Nicolas Dorier
sumber
9
Meskipun penting untuk memiliki keberanian dan inisiatif untuk menantang kebijaksanaan yang diterima saat yang tepat, saya pikir adalah bijaksana untuk terlebih dahulu memahami mengapa kebijaksanaan yang diterima telah diterima sejak awal, sebelum memulai untuk menentangnya. -1 karena "Abaikan pedoman itu!" bukan jawaban yang baik untuk "Mengapa pedoman ini ada?" Kebetulan, komunitas tidak hanya "menyalahkan saya" dalam kasus ini, tetapi memberikan penjelasan yang memuaskan yang berhasil membujuk saya.
Biasa
3
Sebenarnya, ketika tidak ada penjelasan yang dibuat, satu-satunya cara Anda adalah mendorong batas dan menguji tidak takut pelanggaran. Sekarang. Tanyakan pada diri sendiri, bahkan jika Daftar <T> bukan ide yang baik. Berapa banyak rasa sakit untuk mengubah warisan dari Daftar <T> ke Koleksi <T>? Tebakan: Lebih sedikit waktu daripada memposting di forum, berapa pun panjang kode Anda dengan alat refactoring. Sekarang ini adalah pertanyaan yang bagus, tetapi bukan yang praktis.
Nicolas Dorier
Untuk memperjelas: Saya tentu tidak menunggu berkat SO untuk mewarisi dari apa pun yang saya inginkan namun saya menginginkannya, tetapi inti pertanyaan saya adalah untuk memahami pertimbangan yang harus masuk ke dalam keputusan ini, dan mengapa begitu banyak programmer berpengalaman tampaknya memiliki memutuskan kebalikan dari apa yang saya lakukan. Collection<T>tidak sama dengan List<T>itu mungkin memerlukan sedikit kerja untuk memverifikasi dalam proyek besar-ish.
Biasa
2
team.ByName("Nicolas")berarti "Nicolas"adalah nama tim.
Sam Leach
1
"jangan tambahkan kendala yang seharusnya tidak ada" Au contraire, jangan memaparkan apa pun yang kamu tidak punya alasan bagus untuk mengekspos. Ini bukan kemurnian atau kefanatikan buta, juga bukan tren mode desain terbaru. Ini adalah desain berorientasi objek dasar 101, tingkat pemula. Bukan rahasia mengapa "aturan" ini ada, itu adalah hasil dari beberapa dekade pengalaman.
sara
8

Apa yang dikatakan pedoman adalah bahwa API publik tidak boleh mengungkapkan keputusan desain internal apakah Anda menggunakan daftar, set, kamus, pohon atau apa pun. "Tim" belum tentu daftar. Anda dapat mengimplementasikannya sebagai daftar tetapi pengguna API publik Anda harus menggunakan kelas Anda berdasarkan kebutuhan untuk mengetahui. Ini memungkinkan Anda mengubah keputusan dan menggunakan struktur data yang berbeda tanpa memengaruhi antarmuka publik.

QuentinUK
sumber
Dalam retrospeksi, setelah penjelasan @EricLippert dan yang lainnya, Anda sebenarnya telah memberikan jawaban yang bagus untuk bagian API dari pertanyaan saya - selain apa yang Anda katakan, jika saya melakukannya class FootballTeam : List<FootballPlayers>, pengguna kelas saya akan dapat memberi tahu saya ' telah diwarisi dari List<T>dengan menggunakan refleksi, melihat List<T>metode yang tidak masuk akal bagi sebuah tim sepak bola, dan mampu menggunakan FootballTeamdalam List<T>, jadi saya akan menjadi mengungkapkan rincian implementasi untuk klien (tidak perlu).
Biasa
7

Ketika mereka mengatakan List<T>"dioptimalkan", saya pikir mereka ingin berarti itu tidak memiliki fitur seperti metode virtual yang sedikit lebih mahal. Jadi masalahnya adalah bahwa setelah Anda mengekspos List<T>di Anda API umum , Anda kehilangan kemampuan untuk menegakkan aturan bisnis atau menyesuaikan fungsi nanti. Tetapi jika Anda menggunakan kelas bawaan ini sebagai internal dalam proyek Anda (sebagai lawan dari kemungkinan terpapar ke ribuan pelanggan / mitra / tim Anda sebagai API) maka mungkin OK jika menghemat waktu Anda dan itu adalah fungsi yang ingin Anda gunakan. duplikat. Keuntungan dari mewarisi dari List<T>adalah Anda menghilangkan banyak kode pembungkus bisu yang tidak akan pernah dikustomisasi di masa mendatang. Juga jika Anda ingin kelas Anda secara eksplisit memiliki semantik yang sama persisList<T> untuk kehidupan API Anda maka mungkin juga OK.

Saya sering melihat banyak orang melakukan banyak pekerjaan tambahan hanya karena aturan FxCop mengatakan demikian atau blog seseorang mengatakan itu adalah praktik "buruk". Sering kali, ini mengubah kode untuk merancang pola keanehan palooza. Seperti halnya banyak pedoman, perlakukan sebagai pedoman yang dapat memiliki pengecualian.

Shital Shah
sumber
4

Walaupun saya tidak memiliki perbandingan yang kompleks seperti kebanyakan jawaban ini, saya ingin membagikan metode saya untuk menangani situasi ini. Dengan memperluas IEnumerable<T>, Anda dapat mengizinkan Teamkelas Anda untuk mendukung ekstensi permintaan Linq, tanpa secara terbuka memperlihatkan semua metode dan properti List<T>.

class Team : IEnumerable<Player>
{
    private readonly List<Player> playerList;

    public Team()
    {
        playerList = new List<Player>();
    }

    public Enumerator GetEnumerator()
    {
        return playerList.GetEnumerator();
    }

    ...
}

class Player
{
    ...
}
Null511
sumber
3

Jika pengguna kelas Anda membutuhkan semua metode dan properti ** yang dimiliki Daftar, Anda harus mengambil kelas Anda darinya. Jika mereka tidak membutuhkannya, lampirkan Daftar dan buat pembungkus untuk metode yang benar-benar dibutuhkan pengguna kelas Anda.

Ini adalah aturan ketat, jika Anda menulis API publik , atau kode lain apa pun yang akan digunakan oleh banyak orang. Anda dapat mengabaikan aturan ini jika Anda memiliki aplikasi kecil dan tidak lebih dari 2 pengembang. Ini akan menghemat waktu Anda.

Untuk aplikasi kecil, Anda juga dapat mempertimbangkan untuk memilih bahasa lain yang tidak terlalu ketat. Ruby, JavaScript - apa pun yang memungkinkan Anda menulis lebih sedikit kode.

Ivan Nikitin
sumber
3

Saya hanya ingin menambahkan bahwa Bertrand Meyer, penemu Eiffel dan desain dengan kontrak, akan Teammewarisi dari List<Player>tanpa banyak mengedipkan kelopak mata.

Dalam bukunya, Object-Oriented Software Construction , ia membahas implementasi sistem GUI di mana jendela persegi panjang dapat memiliki jendela anak. Dia hanya memiliki Windowwarisan dari keduanya Rectangledan Tree<Window>menggunakan kembali implementasi.

Namun, C # bukan Eiffel. Yang terakhir mendukung banyak pewarisan dan penggantian nama fitur . Di C #, ketika Anda subkelas, Anda mewarisi antarmuka dan implementasi. Anda dapat mengesampingkan implementasinya, tetapi konvensi pemanggilan disalin langsung dari superclass. Namun, di Eiffel, Anda dapat memodifikasi nama metode publik, sehingga Anda dapat mengganti nama Adddan Removemenjadi Hiredan Firedi Anda Team. Jika instance dari Teamdisadap kembali ke List<Player>, pemanggil akan menggunakan Adddan Removememodifikasinya, tetapi metode virtual Anda Hiredan Fireakan dipanggil.

Alexey
sumber
1
Di sini masalahnya bukan multiple inheritance, mixin (dll.) Atau bagaimana cara mengatasi ketidakhadiran mereka. Dalam bahasa apa pun, bahkan dalam bahasa manusia, sebuah tim bukanlah daftar orang tetapi terdiri dari daftar orang. Ini adalah konsep abstrak. Jika Bertrand Meyer atau siapa pun yang mengelola sebuah tim dengan mensubklasifikasikan Daftar adalah melakukan kesalahan. Anda harus mensubklasifikasikan Daftar jika Anda ingin jenis daftar yang lebih spesifik. Semoga Anda setuju.
Mauro Sampietro
1
Itu tergantung pada apa warisan bagi Anda. Dengan sendirinya, ini adalah operasi abstrak, itu tidak berarti dua kelas berada dalam hubungan "adalah jenis khusus", meskipun itu adalah interpretasi arus utama. Namun, pewarisan dapat diperlakukan sebagai mekanisme untuk digunakan kembali jika desain bahasa mendukungnya, meskipun komposisi adalah alternatif yang diinginkan saat ini.
Alexey
2

Saya pikir saya tidak setuju dengan generalisasi Anda. Sebuah tim bukan hanya kumpulan pemain. Sebuah tim memiliki lebih banyak informasi tentangnya - nama, lambang, kumpulan staf manajemen / admin, kumpulan kru pelatihan, kemudian koleksi pemain. Jadi, dengan benar, kelas FootballTeam Anda harus memiliki 3 koleksi dan tidak dengan sendirinya menjadi koleksi; jika ingin memodelkan dunia nyata dengan benar.

Anda dapat mempertimbangkan kelas PlayerCollection yang seperti StringCollection Khusus menawarkan beberapa fasilitas lain - seperti validasi dan pemeriksaan sebelum objek ditambahkan atau dihapus dari toko internal.

Mungkin, gagasan atasan PlayerCollection sesuai dengan pendekatan pilihan Anda?

public class PlayerCollection : Collection<Player> 
{ 
}

Dan FootballTeam dapat terlihat seperti ini:

public class FootballTeam 
{ 
    public string Name { get; set; }
    public string Location { get; set; }

    public ManagementCollection Management { get; protected set; } = new ManagementCollection();

    public CoachingCollection CoachingCrew { get; protected set; } = new CoachingCollection();

    public PlayerCollection Players { get; protected set; } = new PlayerCollection();
}
Eniola
sumber
2

Lebih suka Antarmuka daripada Kelas

Kelas harus menghindari berasal dari kelas dan bukannya mengimplementasikan antarmuka minimal yang diperlukan.

Pewarisan memecah Enkapsulasi

Berasal dari istirahat kelas enkapsulasi :

  • memperlihatkan detail internal tentang bagaimana koleksi Anda diimplementasikan
  • mendeklarasikan antarmuka (sekumpulan fungsi dan properti publik) yang mungkin tidak sesuai

Di antara hal-hal lain ini mempersulit untuk memperbaiki kode Anda.

Kelas adalah Detail Implementasi

Kelas adalah detail implementasi yang harus disembunyikan dari bagian lain dari kode Anda. Singkatnya System.Listadalah implementasi spesifik dari tipe data abstrak, yang mungkin atau mungkin tidak sesuai sekarang dan di masa depan.

Secara konseptual, fakta bahwa System.Listtipe data disebut "daftar" adalah sedikit ikan haring merah. A System.List<T>adalah kumpulan pesanan yang dapat diubah yang mendukung operasi diamortisasi O (1) untuk menambah, menyisipkan, dan menghapus elemen, dan operasi O (1) untuk mengambil jumlah elemen atau mendapatkan dan mengatur elemen berdasarkan indeks.

Semakin Kecil Antarmuka, semakin Fleksibel Kode

Saat mendesain struktur data, semakin sederhana antarmuka, semakin fleksibel kodenya. Lihat saja seberapa kuat LINQ untuk demonstrasi ini.

Cara Memilih Antarmuka

Ketika Anda berpikir "daftar" Anda harus mulai dengan mengatakan pada diri sendiri, "Saya perlu mewakili koleksi pemain baseball". Jadi katakanlah Anda memutuskan untuk memodelkan ini dengan kelas. Apa yang harus Anda lakukan pertama adalah memutuskan apa jumlah minimal antarmuka yang perlu diekspos oleh kelas ini.

Beberapa pertanyaan yang dapat membantu memandu proses ini:

  • Apakah saya perlu menghitung? Jika tidak mempertimbangkan implementasiIEnumerable<T>
  • Apakah koleksi ini akan berubah setelah diinisialisasi? Jika tidak mempertimbangkan IReadonlyList<T>.
  • Apakah penting bahwa saya dapat mengakses item berdasarkan indeks? MempertimbangkanICollection<T>
  • Apakah urutan saya menambahkan item ke koleksi penting? Mungkin itu sebuah ISet<T>?
  • Jika Anda memang menginginkan hal ini maka lanjutkan dan laksanakan IList<T>.

Dengan cara ini Anda tidak akan menggabungkan bagian lain dari kode ke detail implementasi dari koleksi pemain baseball Anda dan akan bebas untuk mengubah cara penerapannya selama Anda menghormati antarmuka.

Dengan mengambil pendekatan ini, Anda akan menemukan bahwa kode menjadi lebih mudah dibaca, refactor, dan digunakan kembali.

Catatan tentang Menghindari Boilerplate

Menerapkan antarmuka dalam IDE modern harus mudah. Klik kanan dan pilih "Implementasikan Antarmuka". Kemudian teruskan semua implementasi ke kelas anggota jika perlu.

Yang mengatakan, jika Anda menemukan Anda sedang menulis banyak boilerplate, itu berpotensi karena Anda mengekspos lebih banyak fungsi daripada yang seharusnya. Itu adalah alasan yang sama Anda tidak boleh mewarisi dari kelas.

Anda juga dapat mendesain antarmuka yang lebih kecil yang masuk akal untuk aplikasi Anda, dan mungkin hanya beberapa fungsi ekstensi pembantu untuk memetakan antarmuka tersebut ke yang lain yang Anda butuhkan. Ini adalah pendekatan yang saya ambil di IArrayantarmuka saya sendiri untuk pustaka LinqArray .

cdiggins
sumber
2

Masalah dengan serialisasi

Satu aspek hilang. Kelas yang mewarisi dari Daftar <> tidak dapat diserialisasi dengan benar menggunakan XmlSerializer . Dalam hal itu DataContractSerializer harus digunakan sebagai gantinya, atau implementasi serialisasi sendiri diperlukan.

public class DemoList : List<Demo>
{
    // using XmlSerializer this properties won't be seralized:
    string AnyPropertyInDerivedFromList { get; set; }     
}

public class Demo
{
    // this properties will be seralized
    string AnyPropetyInDemo { get; set; }  
}

Bacaan lebih lanjut: Ketika sebuah kelas diwarisi dari Daftar <>, XmlSerializer tidak membuat serialisasi atribut lainnya

marsh-goyangkan
sumber
0

Kapan itu bisa diterima?

Mengutip Eric Lippert:

Ketika Anda sedang membangun mekanisme yang memperluas List<T>mekanisme.

Misalnya, Anda bosan dengan tidak adanya AddRangemetode di IList<T>:

public interface IMoreConvenientListInterface<T> : IList<T>
{
    void AddRange(IEnumerable<T> collection);
}

public class MoreConvenientList<T> : List<T>, IMoreConvenientListInterface<T> { }
phoog
sumber