Mari kita bicara tentang polimorfisme parametrik murni dan membahas polimorfisme terikat nanti.
Apa arti parametrik polimorfisme? Nah, itu berarti bahwa suatu tipe, atau lebih tepatnya tipe konstruktor di- parameterkan oleh suatu tipe. Karena jenis dilewatkan sebagai parameter, Anda tidak dapat mengetahui sebelumnya apa itu mungkin. Anda tidak dapat membuat asumsi berdasarkan itu. Sekarang, jika Anda tidak tahu apa itu mungkin, lalu apa gunanya? Apa yang bisa kamu lakukan dengan itu?
Nah, Anda bisa menyimpan dan mengambilnya, misalnya. Itu kasus yang sudah Anda sebutkan: koleksi. Untuk menyimpan item dalam daftar atau array, saya tidak perlu tahu apa-apa tentang item tersebut. Daftar atau larik dapat sepenuhnya mengabaikan jenis.
Tapi bagaimana dengan Maybe
tipenya? Jika Anda tidak terbiasa dengan itu, Maybe
adalah tipe yang mungkin memiliki nilai dan mungkin tidak. Di mana Anda akan menggunakannya? Misalnya, ketika mengeluarkan item dari kamus: fakta bahwa item mungkin tidak ada dalam kamus bukanlah situasi yang luar biasa, jadi Anda benar-benar tidak boleh melempar pengecualian jika item itu tidak ada. Sebagai gantinya, Anda mengembalikan contoh subtipe dari Maybe<T>
, yang memiliki tepat dua subtipe: None
dan Some<T>
. int.Parse
adalah kandidat lain dari sesuatu yang benar-benar harus mengembalikan Maybe<int>
bukannya melemparkan pengecualian atau seluruh int.TryParse(out bla)
tarian.
Sekarang, Anda mungkin berpendapat bahwa Maybe
ini agak seperti daftar yang hanya dapat memiliki nol atau satu elemen. Dan dengan demikian agak-agak koleksi.
Lalu bagaimana dengan Task<T>
? Ini adalah tipe yang berjanji untuk mengembalikan nilai di beberapa titik di masa mendatang, tetapi tidak harus memiliki nilai sekarang.
Atau bagaimana Func<T, …>
? Bagaimana Anda akan merepresentasikan konsep fungsi dari satu tipe ke tipe lain jika Anda tidak dapat abstrak lebih dari tipe?
Atau, lebih umum: mengingat bahwa abstraksi dan penggunaan kembali adalah dua operasi mendasar dari rekayasa perangkat lunak, mengapa Anda tidak ingin dapat abstrak lebih dari tipe?
Jadi, sekarang mari kita bicara tentang polimorfisme terbatas. Polimorfisme terikat pada dasarnya adalah tempat polimorfisme parametrik dan polimorfisme subtipe bertemu: alih-alih konstruktor tipe yang sepenuhnya tidak mengetahui tentang parameter jenisnya, Anda dapat mengikat (atau membatasi) tipe menjadi subtipe dari beberapa tipe tertentu.
Ayo kembali ke koleksi. Ambil hashtable. Kami katakan di atas bahwa daftar tidak perlu tahu apa-apa tentang elemen-elemennya. Yah, hashtable tidak: perlu tahu bahwa itu bisa hash mereka. (Catatan: dalam C #, semua objek dapat hashable, sama seperti semua objek dapat dibandingkan untuk kesetaraan. Namun, itu tidak berlaku untuk semua bahasa, dan kadang-kadang dianggap kesalahan desain bahkan dalam C #.)
Jadi, Anda ingin membatasi parameter tipe Anda untuk jenis kunci dalam hashtable menjadi turunan dari IHashable
:
class HashTable<K, V> where K : IHashable
{
Maybe<V> Get(K key);
bool Add(K key, V value);
}
Bayangkan jika Anda memiliki ini:
class HashTable
{
object Get(IHashable key);
bool Add(IHashable key, object value);
}
Apa yang akan Anda lakukan dengan value
Anda keluar dari sana? Anda tidak dapat melakukan apa pun dengan itu, Anda hanya tahu itu objek. Dan jika Anda mengulanginya, yang Anda dapatkan hanyalah sepasang dari sesuatu yang Anda tahu adalah IHashable
(yang tidak banyak membantu Anda karena hanya memiliki satu properti Hash
) dan sesuatu yang Anda tahu adalah object
(yang membantu Anda lebih sedikit).
Atau sesuatu berdasarkan contoh Anda:
class Repository<T> where T : ISerializable
{
T Get(int id);
void Save(T obj);
void Delete(T obj);
}
Item harus serializable karena akan disimpan di disk. Tetapi bagaimana jika Anda memiliki ini sebagai gantinya:
class Repository
{
ISerializable Get(int id);
void Save(ISerializable obj);
void Delete(ISerializable obj);
}
Dengan kasus generik, jika Anda menempatkan BankAccount
di, Anda mendapatkan BankAccount
kembali, dengan metode dan properti seperti Owner
, AccountNumber
, Balance
, Deposit
, Withdraw
, dll Sesuatu Anda dapat bekerja dengan. Sekarang, yang lainnya? Anda dimasukkan ke dalam BankAccount
tetapi Anda mendapatkan kembali Serializable
, yang hanya memiliki satu properti: AsString
. Apa yang akan kamu lakukan dengan itu?
Ada juga beberapa trik rapi yang dapat Anda lakukan dengan polimorfisme terbatas:
Kuantifikasi F-bounded pada dasarnya adalah di mana variabel tipe muncul lagi dalam kendala. Ini dapat bermanfaat dalam beberapa keadaan. Misalnya bagaimana Anda menulis sebuah ICloneable
antarmuka? Bagaimana Anda menulis metode di mana tipe pengembalian adalah tipe kelas pelaksana? Dalam bahasa dengan fitur MyType , itu mudah:
interface ICloneable
{
public this Clone(); // syntax I invented for a MyType feature
}
Dalam bahasa dengan polimorfisme terbatas, Anda dapat melakukan sesuatu seperti ini sebagai gantinya:
interface ICloneable<T> where T : ICloneable<T>
{
public T Clone();
}
class Foo : ICloneable<Foo>
{
public Foo Clone()
{
// …
}
}
Perhatikan bahwa ini tidak seaman versi MyType, karena tidak ada yang menghentikan seseorang dari hanya meneruskan kelas "salah" ke konstruktor tipe:
class EvilBar : ICloneable<SomethingTotallyUnrelatedToBar>
{
public SomethingTotallyUnrelatedToBar Clone()
{
// …
}
}
Tipe Abstrak Anggota
Ternyata, jika Anda memiliki anggota tipe abstrak dan subtipe, Anda dapat benar-benar bertahan tanpa polimorfisme parametrik dan masih melakukan semua hal yang sama. Scala sedang menuju ke arah ini, menjadi bahasa utama pertama yang dimulai dengan generik dan kemudian mencoba untuk menghapusnya, yang persis sebaliknya dari Jawa dan C #.
Pada dasarnya, di Scala, sama seperti Anda dapat memiliki bidang dan properti serta metode sebagai anggota, Anda juga dapat memiliki jenis. Dan seperti halnya bidang dan properti serta metode dapat dibiarkan abstrak untuk diimplementasikan dalam subkelas nanti, anggota tipe juga dapat dibiarkan abstrak. Mari kita kembali ke koleksi, yang sederhana List
, yang akan terlihat seperti ini, jika didukung di C #:
class List
{
T; // syntax I invented for an abstract type member
T Get(int index) { /* … */ }
void Add(T obj) { /* … */ }
}
class IntList : List
{
T = int;
}
// this is equivalent to saying `List<int>` with generics
interface IFoo<T> where T : IFoo<T>
juga. itu jelas aplikasi kehidupan nyata. contohnya bagus. tetapi untuk beberapa alasan saya tidak merasa puas. Saya lebih suka untuk mendapatkan pikiran saya ketika itu tepat dan ketika tidak. jawaban di sini memiliki kontribusi untuk proses ini, tetapi saya masih merasa tidak nyaman dengan semua ini. itu aneh karena masalah tingkat bahasa sudah lama tidak mengganggu saya.No Anda berpikir terlalu banyak tentang
Repository
, di mana ia adalah hampir sama. Tapi bukan untuk apa obat generik itu ada. Mereka ada di sana untuk para pengguna .Poin kuncinya di sini bukanlah bahwa Repositori itu sendiri lebih umum. Ini karena para pengguna lebih terspesialisasi - yaitu, itu
Repository<BusinessObject1>
danRepository<BusinessObject2>
jenis yang berbeda, dan lebih jauh lagi, bahwa jika saya mengambilRepository<BusinessObject1>
, saya tahu bahwa saya akanBusinessObject1
keluar dariGet
.Anda tidak dapat menawarkan pengetikan yang kuat ini dari warisan sederhana. Kelas Repositori yang Anda usulkan tidak melakukan apa pun untuk mencegah orang-orang membuat repositori yang membingungkan untuk berbagai jenis objek bisnis atau menjamin jenis objek bisnis yang benar akan muncul kembali.
sumber
Object
". Saya juga mengerti mengapa menggunakan obat generik saat menulis koleksi (prinsip KERING). Mungkin, pertanyaan awal saya seharusnya adalah sesuatu tentang menggunakan obat generik di luar konteks koleksi ..IBusinessObject
aBusinessObject1
atau aBusinessObject2
. Itu tidak bisa menyelesaikan kelebihan berdasarkan pada tipe turunan yang tidak diketahui. Itu tidak bisa menolak kode yang masuk dalam tipe yang salah. Ada sejuta bit dari pengetikan yang lebih kuat yang tidak bisa dilakukan oleh Intellisense sama sekali. Dukungan perkakas yang lebih baik adalah manfaat yang bagus tetapi benar-benar tidak ada hubungannya dengan alasan intinya."kemungkinan besar klien dari Repositori ini ingin mendapatkan dan menggunakan objek melalui antarmuka IBusinessObject".
Tidak, mereka tidak akan melakukannya.
Mari kita pertimbangkan bahwa IBusinessObject memiliki definisi berikut:
Itu hanya mendefinisikan Id karena itu adalah satu-satunya fungsi bersama antara semua objek bisnis. Dan Anda memiliki dua objek bisnis yang sebenarnya: Orang dan Alamat (karena Orang tidak memiliki jalan dan Alamat tidak memiliki nama, Anda tidak dapat membatasi keduanya ke antarmuka umum dengan fungsi keduanya. Itu akan menjadi desain yang mengerikan, melanggar Prinsip Segragation Antarmuka , "I" dalam SOLID )
Sekarang, apa yang terjadi ketika Anda menggunakan versi generik repositori:
Ketika Anda memanggil metode Dapatkan di repositori generik objek yang dikembalikan akan sangat diketik, memungkinkan Anda untuk mengakses semua anggota kelas.
Di sisi lain, ketika Anda menggunakan Gudang non-generik:
Anda hanya akan dapat mengakses anggota dari antarmuka IBusinessObject:
Jadi, kode sebelumnya tidak dapat dikompilasi karena baris berikut:
Tentu, Anda dapat melemparkan IBussinesObject ke kelas aktual, tetapi Anda akan kehilangan semua waktu kompilasi yang diizinkan oleh obat generik (yang mengarah ke InvalidCastExceptions di ujung jalan), akan menderita overhead pemain yang tidak perlu ... Dan bahkan jika Anda tidak peduli kompilasi waktu memeriksa kedua kinerja (Anda harus), casting setelah pasti tidak akan memberi Anda manfaat lebih dari menggunakan obat generik di tempat pertama.
sumber