Mengapa tidak mewarisi dari List <T>?
Ketika merencanakan program saya, saya sering memulai dengan rantai pemikiran seperti ini:
Sebuah tim sepak bola hanyalah daftar pemain sepak bola. Oleh karena itu, saya harus mewakilinya dengan:
var football_team = new List<FootballPlayer>();
Urutan daftar ini mewakili urutan para pemain yang terdaftar dalam daftar.
Namun saya kemudian menyadari bahwa tim juga memiliki properti lain, selain hanya daftar pemain, yang harus dicatat. Misalnya, total skor yang berjalan musim ini, anggaran saat ini, warna seragam, string
mewakili nama tim, dll.
Jadi saya berpikir:
Oke, tim sepak bola itu seperti daftar pemain, tetapi juga memiliki nama (a
string
) dan total skor (anint
). NET tidak menyediakan kelas untuk menyimpan tim sepak bola, jadi saya akan membuat kelas saya sendiri. Struktur yang ada paling mirip dan relevan adalahList<FootballPlayer>
, jadi saya akan mewarisinya:class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal }
Tapi ternyata pedoman mengatakan Anda tidak boleh mewarisiList<T>
. Saya sangat bingung dengan pedoman ini dalam dua hal.
Kenapa tidak?
Rupanya List
entah bagaimana dioptimalkan untuk kinerja . Bagaimana? Masalah kinerja apa yang akan saya sebabkan jika saya memperpanjang List
? Apa sebenarnya yang akan rusak?
Alasan lain yang saya lihat adalah yang List
disediakan oleh Microsoft, dan saya tidak memiliki kendali atasnya, jadi saya tidak dapat mengubahnya nanti, setelah mengekspos "API publik" . Tapi saya kesulitan untuk memahami ini. Apa itu API publik dan mengapa saya harus peduli? Jika proyek saya saat ini tidak dan sepertinya tidak akan pernah memiliki API publik ini, dapatkah saya mengabaikan pedoman ini dengan aman? Jika saya memang mewarisi dari List
dan ternyata saya membutuhkan API publik, kesulitan apa yang akan saya alami?
Mengapa itu penting? Daftar adalah daftar. Apa yang mungkin 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 custom collection, Microsoft telah menyediakan Collection
class yang seharusnya diperpanjang, bukan List
. Tetapi kelas ini sangat kosong, dan tidak memiliki banyak hal berguna, sepertiAddRange
misalnya. Jawaban jvitor83 memberikan alasan kinerja untuk metode tertentu, tetapi bagaimana lambat AddRange
tidak lebih baik daripada tidak AddRange
?
Mewarisi dari Collection
jauh lebih banyak pekerjaan daripada mewarisi dari List
, dan saya melihat tidak ada manfaatnya. Tentunya Microsoft tidak akan menyuruh saya untuk melakukan pekerjaan tambahan tanpa alasan, jadi saya tidak dapat menahan perasaan seperti saya entah bagaimana salah memahami sesuatu, dan mewarisi Collection
sebenarnya bukanlah solusi yang tepat untuk masalah saya.
Saya telah melihat saran seperti penerapan IList
. Tidak. Ini adalah lusinan baris kode boilerplate yang tidak memberi saya apa-apa.
Terakhir, beberapa menyarankan untuk membungkus List
sesuatu:
class FootballTeam
{
public List<FootballPlayer> Players;
}
Ada dua masalah dengan ini:
Itu membuat kode saya bertele-tele. Sekarang saya harus menelepon,
my_team.Players.Count
bukan hanyamy_team.Count
. Untungnya, dengan C # saya dapat mendefinisikan pengindeks untuk membuat pengindeksan transparan, dan meneruskan semua metode internalList
... Tapi itu banyak kode! Apa yang saya dapatkan untuk semua pekerjaan itu?Ini jelas tidak masuk akal. Sebuah tim sepak bola tidak "memiliki" daftar pemain. Ini adalah daftar pemainnya. 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 balik terpal" dapat dikatakan sebagai "menambahkan X ke daftar internal Y", tetapi ini tampak seperti cara berpikir yang sangat kontra-intuitif tentang dunia.
Pertanyaan saya (diringkas)
Apa cara C # yang benar untuk merepresentasikan struktur data, yang, "secara logis" (artinya, "menurut pikiran manusia") hanyalah salah satu list
dari things
dengan beberapa lonceng dan peluit?
Apakah mewarisi dari List<T>
selalu tidak dapat diterima? Kapan itu bisa diterima? Mengapa / mengapa tidak? Apa yang harus dipertimbangkan oleh seorang programmer, ketika memutuskan apakah akan mewarisi List<T>
atau tidak?
Jawaban
Ada beberapa jawaban bagus di sini. Saya akan menambahkan poin-poin berikut kepada mereka.
Apa cara C # yang benar untuk merepresentasikan struktur data, yang, "secara logis" (artinya, "menurut pikiran manusia") hanyalah daftar hal-hal dengan beberapa lonceng dan peluit?
Mintalah sepuluh orang non-programmer komputer yang akrab dengan keberadaan sepak bola untuk mengisi kekosongan:
Tim sepak bola adalah jenis _____ tertentu
Apakah ada yang mengatakan "daftar pemain sepak bola dengan sedikit lonceng dan peluit", atau apakah mereka semua mengatakan "tim olahraga" atau "klub" atau "organisasi"? Gagasan Anda bahwa tim sepak bola adalah jenis daftar pemain tertentu ada dalam pikiran manusia 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; itu memiliki daftar, daftar adalah daftar pemain . Daftar pemain bukanlah jenis daftar pemain tertentu . Daftar adalah daftar pemain. Jadi buatlah sebuah properti bernama Roster
a List<Player>
. Dan lakukan ReadOnlyList<Player>
saat Anda melakukannya, kecuali jika Anda yakin bahwa setiap orang yang mengetahui 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?
Saat Anda sedang membangun mekanisme yang memperluas List<T>
mekanisme tersebut .
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 kode yang banyak! Apa yang saya dapatkan untuk semua pekerjaan itu?
Anda menghabiskan lebih banyak waktu untuk mengetik pertanyaan Anda sehingga Anda perlu menulis metode penerusan untuk anggota yang relevan List<T>
sebanyak lima puluh kali lipat. Anda jelas tidak takut dengan verbositas, dan kita berbicara tentang sejumlah kecil kode di sini; ini adalah pekerjaan beberapa menit.
MEMPERBARUI
Saya memikirkannya lagi dan ada alasan lain untuk tidak mencontoh tim sepak bola sebagai daftar pemain. Sebenarnya, mungkin ide yang buruk untuk mencontohkan tim sepak bola yang memiliki daftar pemain juga. Masalah dengan tim sebagai / memiliki daftar pemain adalah apa yang Anda punya adalah gambaran dari tim pada saat itu . Saya tidak tahu apa kasus bisnis Anda untuk kelas ini, tetapi jika saya memiliki kelas yang mewakili tim sepak bola, saya ingin menanyakan pertanyaan seperti "berapa banyak pemain Seahawks yang melewatkan pertandingan karena cedera antara tahun 2003 dan 2013?" atau "Siapa pemain Denver yang sebelumnya bermain untuk tim lain yang memiliki peningkatan yard lari tahun ke tahun terbesar?" atau " Apakah Piggers sangat sukses tahun ini? "
Artinya, sebuah tim sepak bola menurut saya menjadi model yang baik sebagai kumpulan fakta sejarah seperti ketika seorang pemain direkrut, cedera, pensiun, dll. Jelaslah, daftar pemain saat ini adalah fakta penting yang mungkin harus ada di depan-dan- pusat, tetapi mungkin ada hal menarik lain yang ingin Anda lakukan dengan objek ini yang membutuhkan perspektif yang lebih historis.
Wow, postingan Anda memiliki banyak sekali pertanyaan dan poin. Sebagian besar alasan yang Anda dapatkan dari Microsoft tepat sasaran. Mari kita mulai dengan segala sesuatu tentangList<T>
List<T>
adalah sangat optimal. Penggunaan utamanya adalah untuk digunakan sebagai anggota pribadi 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 melakukannyavar list = new MyList<int, string>();
. - CA1002: Jangan tampilkan daftar umum : Pada dasarnya, meskipun Anda berencana menggunakan aplikasi ini sebagai pengembang tunggal, ada baiknya untuk mengembangkan dengan praktik pengkodean yang baik, sehingga mereka menjadi tertanam dalam diri Anda dan kebiasaan. Anda masih diperbolehkan untuk mengekspos daftar sebagai
IList<T>
jika Anda membutuhkan konsumen untuk memiliki daftar yang diindeks. Ini memungkinkan Anda mengubah implementasi dalam kelas nanti. - Microsoft membuatnya
Collection<T>
sangat umum karena ini adalah konsep umum ... nama mengatakan semuanya; itu hanyalah sebuah koleksi. Ada versi yang lebih tepat sepertiSortedCollection<T>
,ObservableCollection<T>
,ReadOnlyCollection<T>
, dll yang masing-masing melaksanakanIList<T>
tapi tidakList<T>
. Collection<T>
memungkinkan anggota (yaitu Tambah, Hapus, dll.) untuk diganti karena mereka virtual.List<T>
tidak.- Bagian terakhir dari pertanyaan Anda tepat. Sebuah tim sepak bola lebih dari sekedar daftar pemain, jadi itu harus menjadi kelas yang berisi daftar pemain tersebut. Pikirkan Komposisi vs Warisan . Sebuah tim sepak bola memiliki daftar pemain (roster), ini bukan daftar pemain.
Jika saya menulis kode ini, kelasnya 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.
}
class FootballTeam : List<FootballPlayer>
{
public string TeamName;
public int RunningTotal;
}
Kode sebelumnya berarti: sekelompok orang dari jalanan bermain sepak bola, dan mereka kebetulan punya nama. Sesuatu seperti:
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.
}
Artinya: ini adalah tim sepak bola yang memiliki manajemen, pemain, admin, dll. Sesuatu seperti:
Beginilah logika Anda disajikan dalam gambar ...
Ini adalah contoh klasik komposisi vs pewarisan .
Dalam kasus khusus ini:
Apakah tim adalah daftar pemain dengan perilaku tambahan
atau
Apakah tim itu merupakan objek tersendiri yang kebetulan berisi daftar pemain.
Dengan memperluas Daftar, Anda membatasi diri Anda dalam beberapa cara:
Anda tidak dapat membatasi akses (misalnya, menghentikan orang mengubah daftar). Anda mendapatkan semua metode Daftar apakah Anda membutuhkan / menginginkan semuanya atau tidak.
Apa yang terjadi jika Anda ingin memiliki daftar hal-hal lain juga. Misalnya, tim memiliki pelatih, manajer, penggemar, peralatan, dll. Beberapa dari mereka mungkin menjadi daftar dengan sendirinya.
Anda membatasi pilihan Anda untuk warisan. Misalnya Anda mungkin ingin membuat objek Team generik, dan kemudian memiliki BaseballTeam, FootballTeam, dll. Yang mewarisi darinya. Untuk mewarisi dari Daftar Anda perlu melakukan pewarisan dari Tim, tetapi itu berarti bahwa semua jenis tim dipaksa untuk memiliki implementasi yang sama dari daftar itu.
Komposisi - termasuk objek yang memberikan perilaku yang Anda inginkan di dalam objek Anda.
Inheritance - objek Anda menjadi turunan dari objek yang memiliki perilaku yang Anda inginkan.
Keduanya memiliki kegunaannya sendiri, tetapi ini adalah kasus yang jelas di mana komposisi lebih disukai.
Seperti yang telah ditunjukkan oleh semua orang, tim pemain bukanlah daftar pemain. Kesalahan ini dilakukan oleh banyak orang di mana pun, mungkin dengan berbagai tingkat keahlian. Seringkali masalahnya tidak kentara dan terkadang sangat kasar, seperti dalam kasus ini. Desain seperti itu buruk karena melanggar Prinsip Substitusi 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 dipertahankan dalam hubungan Parent / Child antar kelas:
- seorang Anak harus membutuhkan karakteristik yang tidak kurang dari apa yang sepenuhnya mendefinisikan Induk.
- a Orangtua hendaknya tidak memerlukan karakteristik selain apa yang sepenuhnya mendefinisikan Anak.
Dengan kata lain, Parent adalah definisi penting dari seorang anak, dan child adalah definisi yang cukup dari Parent.
Berikut adalah cara untuk memikirkan solusinya dan menerapkan prinsip di atas yang akan membantu seseorang menghindari kesalahan seperti itu. Seseorang harus menguji hipotesisnya dengan memverifikasi apakah 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 daftar berlaku untuk tim dalam arti yang sama)
- Apakah tim adalah kumpulan entitas yang homogen? Ya, tim adalah kumpulan Pemain
- Apakah urutan penyertaan pemain menggambarkan keadaan tim dan apakah tim memastikan bahwa urutannya dipertahankan kecuali diubah secara eksplisit? Tidak, dan Tidak
- Apakah pemain diharapkan untuk dimasukkan / dikeluarkan berdasarkan posisi urutan mereka dalam tim? Tidak
Seperti yang Anda lihat, hanya karakteristik pertama dari daftar yang berlaku untuk tim. Oleh karena itu, tim bukanlah daftar. Daftar akan menjadi detail implementasi bagaimana Anda mengelola tim Anda, jadi daftar tersebut hanya boleh digunakan untuk menyimpan objek pemain dan dimanipulasi dengan metode kelas Tim.
Pada titik ini saya ingin mengatakan bahwa kelas Tim seharusnya, menurut pendapat saya, tidak diimplementasikan dengan menggunakan Daftar; itu harus diimplementasikan menggunakan struktur data Set (HashSet, misalnya) dalam banyak kasus.
Bagaimana jika FootballTeam
memiliki tim cadangan bersama dengan tim utama?
class FootballTeam
{
List<FootballPlayer> Players { get; set; }
List<FootballPlayer> ReservePlayers { get; set; }
}
Bagaimana Anda memodelkannya?
class FootballTeam : List<FootballPlayer>
{
public string TeamName;
public int RunningTotal
}
Hubungan tersebut jelas memiliki a dan bukan adalah a .
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 sebuah koleksi, beri nama kelasnya SomethingCollection
.
Apakah Anda secara SomethingCollection
semantik masuk akal? Lakukan ini hanya jika tipe Anda adalah kumpulan dari Something
.
Dalam kasus FootballTeam
ini tidak terdengar benar. A Team
lebih dari a Collection
. A Team
dapat memiliki pelatih, pelatih, dll seperti yang ditunjukkan oleh jawaban lain.
FootballCollection
terdengar seperti koleksi bola atau mungkin koleksi perlengkapan sepak bola. TeamCollection
, kumpulan tim.
FootballPlayerCollection
terdengar seperti kumpulan pemain yang akan menjadi nama yang valid untuk kelas yang diwarisi dari List<FootballPlayer>
jika Anda benar-benar ingin melakukannya.
Benar List<FootballPlayer>
-benar tipe yang sangat bagus untuk ditangani. Mungkin IList<FootballPlayer>
jika Anda mengembalikannya dari suatu metode.
Singkatnya
Bertanya pada diri sendiri
Apakah
X
aY
? atau MemilikiX
sebuahY
?Apakah nama kelas saya berarti apa?
Desain> Implementasi
Metode dan properti apa yang Anda ungkapkan adalah keputusan desain. 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 adalah:
- Data apa yang tercakup dalam objek ini dalam model yang saya buat?
- Perilaku apa yang ditunjukkan objek ini dalam model itu?
- Bagaimana ini bisa berubah di masa depan?
Ingatlah bahwa warisan menyiratkan hubungan "isa" (adalah a), sedangkan komposisi menyiratkan hubungan "memiliki" (hasa). Pilih yang tepat untuk situasi Anda dalam pandangan Anda, dengan mengingat ke mana hal-hal mungkin akan pergi saat aplikasi Anda berkembang.
Pertimbangkan untuk 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 bukanlah sesuatu yang dilakukan semua orang secara sadar pada level ini dalam pengkodean sehari-hari. Tetapi jika Anda mempertimbangkan topik semacam ini, Anda sedang melangkah di perairan desain. Menyadarinya bisa membebaskan.
Pertimbangkan Spesifikasi Desain
Lihatlah List <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 pada FootballTeam menurut pandangan Anda?
Apakah footballTeam.Reverse () masuk akal bagi Anda? Apakah footballTeam.ConvertAll <TOutput> () terlihat seperti yang Anda inginkan?
Ini bukan pertanyaan jebakan; jawabannya mungkin benar-benar "ya". Jika Anda mengimplementasikan / mewarisi List <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 diperlakukan sebagai kumpulan / daftar pemain (perilaku), dan oleh karena itu Anda ingin menerapkan ICollection atau IList, dengan segala cara, lakukanlah. Secara nosional:
class FootballTeam : ... ICollection<Player>
{
...
}
Jika Anda ingin objek Anda berisi koleksi / daftar pemain (data), dan karenanya Anda ingin koleksi atau daftar tersebut menjadi properti atau anggota, lakukanlah. Secara nosional:
class FootballTeam ...
{
public ICollection<Player> Players { get { ... } }
}
Anda mungkin merasa bahwa Anda ingin orang-orang hanya dapat menghitung set pemain, daripada menghitungnya, menambahkan atau menghapusnya. IEnumerable <Player> adalah opsi yang sangat valid untuk dipertimbangkan.
Anda mungkin merasa bahwa tidak satu pun dari antarmuka ini berguna sama sekali dalam model Anda. Ini kecil kemungkinannya (IEnumerable <T> berguna dalam banyak situasi) tetapi masih memungkinkan.
Siapa pun yang mencoba memberi tahu Anda bahwa salah satunya secara kategoris dan pasti salah dalam setiap kasus adalah salah arah. Siapa pun yang mencoba untuk memberitahu Anda itu adalah kategoris dan definitif yang tepat dalam setiap kasus yang sesat.
Pindah ke Implementasi
Setelah Anda memutuskan data dan perilaku, Anda dapat membuat keputusan tentang penerapan. Ini termasuk kelas beton mana yang Anda andalkan melalui pewarisan atau komposisi.
Ini mungkin bukan langkah besar, dan orang sering menyamakan desain dan implementasi karena sangat mungkin untuk menjalankan semuanya di kepala Anda dalam satu atau dua detik dan mulai mengetik.
Eksperimen Pikiran
Contoh artifisial: seperti yang disebutkan orang lain, sebuah tim tidak selalu "hanya" kumpulan pemain. Apakah Anda menyimpan koleksi skor pertandingan untuk tim? Apakah tim dapat dipertukarkan dengan klub, dalam 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 demikian, desain, pada titik ini di C # Anda tidak akan dapat menerapkan semua ini dengan mewarisi dari Daftar <T> bagaimanapun, karena C # "hanya" mendukung pewarisan tunggal. (Jika Anda telah mencoba malarky ini di C ++, Anda dapat menganggap ini sebagai Good Thing.) Menerapkan satu koleksi melalui pewarisan dan satu melalui komposisi kemungkinan akan terasa kotor. Dan properti seperti Count menjadi membingungkan bagi pengguna kecuali Anda mengimplementasikan ILIst <Player> .Count dan IList <StaffMember> .Count dll secara eksplisit, dan kemudian mereka hanya menyakitkan daripada membingungkan. Anda dapat melihat ke mana arahnya; firasat sementara memikirkan jalan ini mungkin memberi tahu Anda bahwa merasa salah untuk menuju ke arah ini (dan benar atau salah, kolega Anda mungkin juga jika Anda menerapkannya dengan cara ini!)
Jawaban Singkat (Terlambat)
Panduan 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. Lebih umum dengan objek domain / dunia nyata untuk menemukan hubungan "hasa" yang berguna dan konsisten daripada hubungan "isa" yang berguna dan konsisten kecuali Anda jauh di dalam abstrak, terutama seiring berjalannya waktu dan data serta perilaku objek dalam kode yang tepat perubahan. Ini seharusnya tidak menyebabkan Anda selalu mengesampingkan mewarisi dari kelas koleksi; tapi mungkin sugestif.
Pertama-tama, ini berkaitan dengan kegunaan. Jika Anda menggunakan warisan, Team
kelas 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, Anda mungkin menginginkan 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 yang disebutkan, komposisi adalah cara yang tepat untuk melakukannya. Cukup terapkan daftar pemain sebagai variabel pribadi.
Sebuah tim sepak bola bukanlah daftar pemain sepak bola. Sebuah 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
}
Izinkan saya menulis ulang pertanyaan Anda. jadi Anda mungkin melihat subjek dari perspektif yang berbeda.
Ketika saya perlu mewakili tim sepak bola, saya mengerti bahwa itu pada dasarnya adalah sebuah nama. Seperti: "The Eagles"
string team = new string();
Kemudian saya menyadari tim juga memiliki pemain.
Mengapa saya tidak bisa memperpanjang tipe string sehingga itu juga menyimpan daftar pemain?
Titik masuk Anda ke dalam masalah ini sewenang-wenang. Cobalah untuk berpikir apa yang tim memiliki (sifat), bukan apa yang .
Setelah Anda melakukannya, Anda dapat melihat apakah properti berbagi dengan kelas lain. Dan pikirkan tentang warisan.
Itu tergantung konteksnya
Ketika Anda menganggap 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 terlalu tumpang tindih. Konteks lainnya adalah saat ini versus mantan pemain, dll.
Semantik tidak jelas
Jadi masalah dengan mempertimbangkan tim sebagai daftar pemainnya adalah bahwa semantiknya bergantung pada konteksnya dan tidak dapat diperpanjang ketika konteksnya berubah. Selain itu, sulit untuk mengungkapkan konteks mana yang Anda gunakan.
Kelas dapat dikembangkan
Ketika Anda menggunakan kelas dengan hanya satu anggota (misalnya IList activePlayers
), Anda dapat menggunakan nama anggota (dan tambahan komentarnya) untuk membuat konteksnya jelas. Jika ada konteks tambahan, Anda tinggal menambahkan anggota tambahan.
Kelas lebih kompleks
Dalam beberapa kasus mungkin berlebihan untuk membuat kelas ekstra. Setiap definisi kelas harus dimuat melalui classloader dan akan di-cache oleh mesin virtual. Ini membebani Anda kinerja runtime dan memori. Jika Anda memiliki konteks yang sangat spesifik, mungkin tidak masalah untuk mempertimbangkan tim sepak bola sebagai daftar pemain. Tapi dalam kasus ini, Anda harus benar-benar menggunakan a IList
, bukan kelas turunan darinya.
Kesimpulan / Pertimbangan
Ketika Anda memiliki konteks yang sangat spesifik, tidak apa-apa untuk mempertimbangkan tim sebagai daftar pemain. Misalnya di dalam metode, Anda boleh menulis:
IList<Player> footballTeam = ...
Saat menggunakan F #, bahkan boleh saja membuat singkatan tipe:
type FootballTeam = IList<Player>
Tetapi jika konteksnya lebih luas atau bahkan tidak jelas, Anda sebaiknya tidak melakukan ini. Ini terutama terjadi ketika Anda membuat kelas baru yang konteksnya dapat digunakan di masa mendatang 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 tempat kelas akan digunakan tidak tetap dan akan berubah di masa mendatang. Dalam hal ini Anda tidak dapat menganggap tim sebagai daftar pemain, tetapi Anda harus mencontoh daftar pemain (saat ini aktif, tidak cedera, dll.) Sebagai atribut tim.
Hanya karena saya pikir jawaban lain cukup banyak bersinggungan dengan apakah tim sepak bola "is-a" List<FootballPlayer>
atau "has-a" List<FootballPlayer>
, yang sebenarnya tidak menjawab pertanyaan ini seperti yang tertulis.
OP terutama meminta klarifikasi tentang pedoman untuk mewarisi dari List<T>
:
Sebuah pedoman mengatakan bahwa Anda tidak boleh mewarisi
List<T>
. Kenapa tidak?
Karena List<T>
tidak memiliki metode virtual. Ini bukan masalah dalam kode Anda sendiri, karena Anda biasanya dapat mengganti implementasi dengan sedikit kesulitan - tetapi bisa menjadi masalah yang jauh lebih besar dalam API publik.
Apa itu API publik dan mengapa saya harus peduli?
API publik adalah antarmuka yang Anda tunjukkan kepada pemrogram pihak ketiga. Pikirkan kode kerangka kerja. Dan ingat bahwa pedoman yang direferensikan adalah " Pedoman Desain .NET Framework " dan bukan " Pedoman Desain Aplikasi .NET ". 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 mengabaikan pedoman ini dengan aman? Jika saya mewarisi dari List dan ternyata saya membutuhkan API publik, kesulitan apa yang akan saya alami?
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 pembuatan versi (yang, ini adalah subset).
Jika Anda menambahkan API publik di masa mendatang, Anda akan perlu mengabstraksi API Anda dari implementasi Anda (dengan tidak mengekspos Anda List<T>
secara langsung) atau melanggar pedoman dengan kemungkinan masalah di masa mendatang.
Mengapa itu penting? Daftar adalah daftar. Apa yang mungkin berubah? Apa yang mungkin ingin saya ubah?
Tergantung pada konteksnya, tetapi karena kami menggunakan FootballTeam
sebagai contoh - bayangkan Anda tidak dapat menambahkan FootballPlayer
jika itu akan menyebabkan tim melampaui batas 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 Add
karena bukan virtual
(untuk alasan kinerja).
Jika Anda berada dalam sebuah aplikasi (yang pada dasarnya berarti bahwa Anda dan semua pemanggil Anda dikompilasi bersama) maka Anda sekarang dapat mengubah untuk menggunakan IList<T>
dan memperbaiki kesalahan kompilasi apa pun:
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 publik terpapar ke pihak ketiga, Anda baru saja membuat perubahan yang dapat menyebabkan kesalahan kompilasi dan / atau runtime.
TL; DR - pedoman untuk API publik. Untuk API pribadi, lakukan apa yang Anda inginkan.
Apakah memungkinkan orang untuk mengatakannya
myTeam.subList(3, 5);
masuk akal sama sekali? Jika tidak, maka itu seharusnya bukan Daftar.
Itu tergantung pada perilaku objek "tim" Anda. Jika berperilaku seperti koleksi, mungkin OK untuk merepresentasikannya terlebih dahulu dengan List biasa. Kemudian Anda mungkin mulai memperhatikan bahwa Anda terus menggandakan kode yang berulang pada daftar; pada titik ini Anda memiliki opsi untuk membuat objek FootballTeam yang membungkus daftar pemain. Kelas FootballTeam menjadi rumah bagi semua kode yang berulang pada daftar pemain.
Itu membuat kode saya bertele-tele. Sekarang saya harus memanggil my_team.Players.Count, bukan hanya my_team.Count. Untungnya, dengan C # saya dapat menentukan pengindeks untuk membuat pengindeksan transparan, dan meneruskan semua metode Daftar internal ... Tapi itu banyak kode! Apa yang saya dapatkan untuk semua pekerjaan itu?
Enkapsulasi. Klien Anda tidak perlu tahu apa yang terjadi di dalam FootballTeam. Untuk semua klien Anda tahu, itu mungkin diimplementasikan dengan melihat daftar pemain di database. Mereka tidak perlu tahu, dan ini meningkatkan desain Anda.
Ini jelas tidak masuk akal. Sebuah tim sepak bola tidak "memiliki" daftar pemain. Ini adalah daftar pemainnya. 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.
Ada banyak jawaban bagus di sini, tapi 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 satu ini tidak perlu mengkhawatirkan semuanya. Faktanya, Anda ingin melangkah lebih jauh dan secara aktif mencegah objek lain melewati internal ini.
Saat 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 saat pemain pergi dengan mengetahui apakah mereka pensiun, mengundurkan diri atau dipecat. Anda bisa mengimplementasikan RemovePlayer
metode yang menggunakan enum masukan yang sesuai. Namun, dengan mewarisi dari List
, Anda tidak akan dapat mencegah akses langsung ke Remove
, RemoveAll
dan bahkan Clear
. Akibatnya, Anda benar-benar melemahkanFootballTeam
kelas Anda .
Pikiran tambahan tentang enkapsulasi ... Anda mengemukakan kekhawatiran berikut:
Itu membuat kode saya bertele-tele. Sekarang saya harus memanggil my_team.Players.Count, bukan hanya my_team.Count.
Anda benar, itu tidak perlu bertele-tele bagi semua klien untuk menggunakan tim Anda. Namun, masalah itu sangat kecil dibandingkan dengan fakta bahwa Anda telah terpapar List Players
pada semua orang sehingga mereka dapat bermain-main dengan tim Anda tanpa persetujuan Anda.
Anda melanjutkan dengan mengatakan:
Ini jelas tidak masuk akal. Sebuah tim sepak bola tidak "memiliki" daftar pemain. Ini adalah daftar pemainnya. Anda tidak mengatakan "John McFootballer telah bergabung dengan pemain SomeTeam". Anda mengatakan "John telah bergabung dengan SomeTeam".
Anda salah tentang bit pertama: Hapus kata 'daftar', dan sebenarnya jelas bahwa sebuah tim memang memiliki pemain.
Namun, Anda memukul paku di kepala dengan yang kedua. Anda tidak ingin klien menelepon ateam.Players.Add(...)
. Anda ingin mereka menelepon ateam.AddPlayer(...)
. Dan implementasi Anda akan (mungkin antara lain) memanggil secara Players.Add(...)
internal.
Semoga Anda dapat melihat betapa pentingnya enkapsulasi untuk tujuan memberdayakan objek Anda. Anda ingin mengizinkan setiap kelas melakukan tugasnya dengan baik tanpa takut gangguan dari objek lain.
Apa cara C # yang benar untuk merepresentasikan struktur data ...
Ingat, "Semua model salah, tetapi beberapa berguna." - Kotak George EP
Tidak ada "cara yang benar", hanya cara yang berguna.
Pilih salah satu yang berguna bagi Anda dan / pengguna Anda. Itu dia. Kembangkan secara ekonomi, jangan rekayasa berlebihan. Semakin sedikit kode yang Anda tulis, semakin sedikit kode yang perlu Anda debug. (baca edisi berikut).
- Diedit
Jawaban terbaik saya adalah ... tergantung. Mewarisi dari Daftar akan mengekspos klien kelas ini ke metode yang mungkin tidak boleh diekspos, terutama karena FootballTeam terlihat seperti entitas bisnis.
- Edisi 2
Saya benar-benar tidak ingat apa yang saya maksud di komentar "jangan terlalu merekayasa". Meskipun saya percaya pola pikir KISS adalah panduan yang baik, saya ingin menekankan bahwa mewarisi kelas bisnis dari List akan menciptakan lebih banyak masalah daripada menyelesaikannya, karena kebocoran abstraksi .
Di sisi lain, saya percaya ada sejumlah kasus yang hanya berguna untuk mewarisi dari List. 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.
Ini mengingatkan saya pada "Apakah" versus "memiliki" pengorbanan. Terkadang lebih mudah dan lebih masuk akal untuk mewarisi langsung dari kelas super. Di lain waktu, 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 ke antarmuka atau batasan lain yang mungkin datang dari mewarisi dari kelas.
Yang mana yang kamu lakukan Seperti banyak hal lainnya ... itu tergantung pada konteksnya. Panduan yang akan saya gunakan adalah bahwa untuk mewarisi dari kelas lain harus ada hubungan "ada". Jadi jika Anda menulis kelas yang disebut BMW, itu bisa mewarisi dari Mobil karena BMW benar-benar sebuah 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 sebuah daftar? Dari apa yang bisa saya katakan, sepertinya Tim tidak benar-benar "adalah" Daftar. Jadi dalam kasus ini, saya akan memiliki List sebagai variabel anggota.
Rahasia kotor saya: Saya tidak peduli apa yang orang katakan, dan saya melakukannya. .NET Framework tersebar dengan "XxxxCollection" (contoh UIElementCollection untuk bagian atas kepala saya).
Jadi apa yang membuat saya berhenti berkata:
team.Players.ByName("Nicolas")
Ketika saya merasa lebih baik daripada
team.ByName("Nicolas")
Selain itu, PlayerCollection saya mungkin digunakan oleh kelas lain, seperti "Klub" tanpa duplikasi kode apa pun.
club.Players.ByName("Nicolas")
Praktik terbaik kemarin, mungkin bukan yang besok. Tidak ada alasan di balik sebagian besar praktik terbaik, sebagian besar hanya kesepakatan luas di antara komunitas. Alih-alih bertanya kepada komunitas apakah itu akan menyalahkan Anda ketika Anda melakukannya tanyakan pada diri sendiri, apa yang lebih mudah dibaca dan dipelihara?
team.Players.ByName("Nicolas")
atau
team.ByName("Nicolas")
Betulkah. Apakah Anda ragu? Sekarang mungkin Anda perlu bermain-main dengan kendala teknis lain yang mencegah Anda menggunakan List <T> dalam kasus penggunaan Anda yang sebenarnya. Tapi jangan tambahkan batasan yang seharusnya tidak ada. Jika Microsoft tidak mendokumentasikan alasannya, maka itu pasti merupakan "praktik terbaik" yang datang entah dari mana.
Apa yang dikatakan pedomannya adalah bahwa API publik tidak boleh mengungkapkan keputusan desain internal apakah Anda menggunakan daftar, kumpulan, kamus, pohon, atau apa pun. Sebuah "tim" belum tentu sebuah daftar. Anda dapat menerapkannya sebagai daftar tetapi pengguna API publik Anda harus menggunakan kelas Anda berdasarkan kebutuhan untuk mengetahui. Hal ini memungkinkan Anda untuk mengubah keputusan Anda dan menggunakan struktur data yang berbeda tanpa mempengaruhi antarmuka publik.
Ketika mereka mengatakan List<T>
"dioptimalkan", saya pikir mereka ingin mengatakan bahwa 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 yang diwariskan ini sebagai internal dalam proyek Anda (sebagai lawan yang berpotensi terpapar ke ribuan pelanggan / mitra / tim lain sebagai API) maka tidak masalah jika itu menghemat waktu Anda dan itu adalah fungsi yang Anda inginkan duplikat. Keuntungan mewarisi dari List<T>
adalah Anda menghilangkan banyak kode pembungkus bodoh yang tidak akan pernah dapat disesuaikan di masa mendatang. Juga jika Anda ingin kelas Anda secara eksplisit memiliki semantik yang sama persis dengan List<T>
masa pakai API Anda, 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 yang "buruk". Berkali-kali, ini mengubah kode menjadi pola desain keanehan palooza. Seperti halnya banyak pedoman, perlakukan itu sebagai pedoman yang bisa memiliki pengecualian.
Meskipun saya tidak memiliki perbandingan yang rumit seperti kebanyakan jawaban ini, saya ingin membagikan metode saya untuk menangani situasi ini. Dengan memperluas IEnumerable<T>
, Anda dapat mengizinkan Team
kelas Anda untuk mendukung ekstensi kueri Linq, tanpa mengekspos semua metode dan properti dari 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
{
...
}
Jika pengguna kelas Anda memerlukan semua metode dan properti yang dimiliki ** List, Anda harus mendapatkan kelas Anda darinya. Jika mereka tidak membutuhkannya, lampirkan List dan buat pembungkus untuk metode yang benar-benar dibutuhkan pengguna kelas Anda.
Ini adalah aturan ketat, jika Anda menulis API publik , atau kode lain 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.
Saya hanya ingin menambahkan bahwa Bertrand Meyer, penemu Eiffel dan desain berdasarkan kontrak, akan Team
mewarisi List<Player>
tanpa mengedipkan kelopak mata.
Dalam bukunya, Konstruksi Perangkat Lunak Berorientasi Objek , ia membahas implementasi sistem GUI di mana jendela persegi panjang dapat memiliki jendela anak. Dia hanya Window
mewarisi dari keduanya Rectangle
dan Tree<Window>
menggunakan kembali implementasinya.
Namun, C # bukanlah Eiffel. Yang terakhir mendukung beberapa pewarisan dan penggantian nama fitur . Di C #, saat Anda membuat subkelas, Anda mewarisi antarmuka dan implementasinya. Anda bisa mengganti implementasinya, tetapi konvensi pemanggilan disalin langsung dari superclass. Namun, di Eiffel, Anda dapat mengubah nama metode publik, sehingga Anda dapat mengganti nama Add
dan Remove
menjadi Hire
dan Fire
di Team
. Jika sebuah instance dari di Team
-upcast kembali ke List<Player>
, pemanggil akan menggunakan Add
dan Remove
mengubahnya, tetapi metode virtual Anda Hire
dan Fire
akan dipanggil.
Masalah dengan serialisasi
Satu aspek hilang. Kelas yang mewarisi dari Daftar <> tidak dapat diserialkan dengan benar menggunakan XmlSerializer . Dalam kasus tersebut 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 kelas diwarisi dari List <>, XmlSerializer tidak membuat serial atribut lainnya
Saya rasa saya tidak setuju dengan generalisasi Anda. Sebuah tim bukan hanya kumpulan pemain. Sebuah tim memiliki lebih banyak informasi tentangnya - nama, lambang, koleksi staf manajemen / admin, kumpulan kru pelatih, kemudian koleksi pemain. Jadi, kelas FootballTeam Anda harus memiliki 3 koleksi dan bukan merupakan koleksi; jika ingin memodelkan dunia nyata dengan benar.
Anda dapat mempertimbangkan kelas PlayerCollection yang seperti Specialized StringCollection menawarkan beberapa fasilitas lain - seperti validasi dan pemeriksaan sebelum objek ditambahkan atau dihapus dari penyimpanan internal.
Mungkin, gagasan tentang atasan PlayerCollection sesuai dengan pendekatan pilihan Anda?
public class PlayerCollection : Collection<Player>
{
}
Dan kemudian FootballTeam akan 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();
}
Lebih memilih Antarmuka daripada Kelas
Kelas harus menghindari pengambilan dari kelas dan sebagai gantinya mengimplementasikan antarmuka minimal yang diperlukan.
Pewarisan Enkapsulasi
Berasal dari kelas memecahkan enkapsulasi :
- mengungkap detail internal tentang bagaimana koleksi Anda diterapkan
- mendeklarasikan antarmuka (sekumpulan fungsi dan properti publik) yang mungkin tidak sesuai
Antara lain, hal ini mempersulit pemfaktoran ulang kode Anda.
Kelas adalah Detail Implementasi
Kelas adalah detail implementasi yang harus disembunyikan dari bagian lain kode Anda. Singkatnya, a System.List
adalah implementasi spesifik dari tipe data abstrak, yang mungkin sesuai atau tidak sesuai sekarang dan di masa depan.
Secara konseptual, fakta bahwa System.List
tipe data disebut "daftar" agak mengganggu. A System.List<T>
adalah koleksi terurut yang dapat berubah yang mendukung operasi O (1) diamortisasi untuk menambahkan, menyisipkan, dan menghapus elemen, dan operasi O (1) untuk mengambil jumlah elemen atau mendapatkan dan menyetel elemen dengan indeks.
Semakin Kecil Antarmuka semakin Fleksibel Kode
Saat mendesain struktur data, semakin sederhana antarmukanya, semakin fleksibel kodenya. Lihat saja seberapa kuat LINQ untuk mendemonstrasikan hal ini.
Bagaimana Memilih Antarmuka
Ketika Anda berpikir tentang "daftar", Anda harus mulai dengan berkata pada diri sendiri, "Saya perlu mewakili sekumpulan pemain bisbol". Jadi katakanlah Anda memutuskan untuk memodelkan ini dengan kelas. Yang harus Anda lakukan pertama kali adalah memutuskan jumlah minimal antarmuka yang perlu diekspos oleh kelas ini.
Beberapa pertanyaan yang dapat membantu memandu proses ini:
- Apakah saya perlu menghitungnya? Jika tidak pertimbangkan untuk menerapkan
IEnumerable<T>
- Apakah koleksi ini akan berubah setelah diinisialisasi? Jika tidak dipertimbangkan
IReadonlyList<T>
. - Apakah penting bahwa saya dapat mengakses item menurut indeks? Mempertimbangkan
ICollection<T>
- Apakah urutan penambahan item ke koleksi itu penting? Mungkinkah itu
ISet<T>
? - Jika Anda memang menginginkan hal ini, lanjutkan dan terapkan
IList<T>
.
Dengan cara ini Anda tidak akan menggabungkan bagian lain dari kode ke detail penerapan dari koleksi pemain bisbol Anda dan akan bebas mengubah cara penerapannya selama Anda menghormati antarmukanya.
Dengan mengambil pendekatan ini, Anda akan menemukan bahwa kode menjadi lebih mudah dibaca, difaktor ulang, dan digunakan kembali.
Catatan tentang Menghindari Boilerplate
Menerapkan antarmuka dalam IDE modern semestinya mudah. Klik kanan dan pilih "Implement Interface". Kemudian teruskan semua implementasi ke kelas anggota jika Anda perlu.
Yang mengatakan, jika Anda menemukan Anda menulis banyak boilerplate, itu berpotensi karena Anda menampilkan 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 antarmuka lain yang Anda butuhkan. Ini adalah pendekatan yang saya ambil di IArray
antarmuka saya sendiri untuk perpustakaan LinqArray .
Kapan itu bisa diterima?
Mengutip Eric Lippert:
Saat Anda sedang membangun mekanisme yang memperluas
List<T>
mekanisme tersebut.
Misalnya, Anda bosan dengan tidak adanya AddRange
metode di IList<T>
:
public interface IMoreConvenientListInterface<T> : IList<T>
{
void AddRange(IEnumerable<T> collection);
}
public class MoreConvenientList<T> : List<T>, IMoreConvenientListInterface<T> { }