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.