Спершу поговоримо про чистий параметричний поліморфізм і підемо згодом на обмежений поліморфізм.
Що означає параметричний поліморфізм? Ну, це означає, що тип, а точніше конструктор типу, параметризується типом. Оскільки тип передається як параметр, ви не можете заздалегідь знати, що це може бути. Ви не можете робити жодних припущень на основі цього. Тепер, якщо ви не знаєте, що це може бути, то в чому користь? Що ти можеш з цим зробити?
Ну, ви можете зберігати та витягувати його, наприклад. Ось той ви вже згадували: колекції. Для того, щоб зберігати предмет у списку чи масиві, мені нічого не потрібно знати про цей предмет. Список або масив можуть бути повністю забутими типом.
А як щодо Maybe
типу? Якщо ви не знайомі з цим, Maybe
це тип, який, можливо, має значення, а може, і ні. Де б ви його використовували? Ну, наприклад, коли витягаєте елемент зі словника: той факт, що елемент може бути не у словнику, не є винятковою ситуацією, тому вам справді не слід викидати виняток, якщо цього елемента немає. Натомість ви повертаєте екземпляр підтипу Maybe<T>
, який має рівно два підтипи: None
і Some<T>
. int.Parse
є ще одним кандидатом чогось, що дійсно повинно повернутись Maybe<int>
замість того, щоб кидати виняток або весь int.TryParse(out bla)
танець.
Тепер ви можете стверджувати, що Maybe
це свого роду схоже на список, який може містити лише нуль або один елемент. І таким чином свого роду колекція.
Тоді про що Task<T>
? Це тип, який обіцяє повернути значення в якийсь момент у майбутньому, але не обов'язково має значення прямо зараз.
Або про що Func<T, …>
? Як би ви представляли поняття функції від одного типу до іншого типу, якщо ви не можете абстрагуватися над типами?
Або, більш загально: враховуючи, що абстракція та повторне використання - це дві основні операції інженерії програмного забезпечення, чому б ви не хотіли мати можливість абстрагуватися над типами?
Отже, тепер поговоримо про обмежений поліморфізм. Обмежений поліморфізм - це в основному там, де зустрічаються параметричний поліморфізм та підтиповий поліморфізм: замість того, щоб конструктор типу повністю не забував про параметр свого типу, ви можете зв’язати (або обмежити) тип, щоб бути підтипом певного типу.
Повернемося до колекцій. Візьміть хештейн. Ми говорили вище, що у списку нічого не потрібно знати про його елементи. Ну, хештел робить: він повинен знати, що він може їх хешировать. (Примітка. У C # всі об'єкти є хешируемими, як і всі об'єкти можна порівняти за рівність. Хоча це не так для всіх мов, але іноді вважається помилкою дизайну навіть у C #.)
Отже, ви хочете обмежити параметр типу для типу ключа в хештелі, щоб він був примірником IHashable
:
class HashTable<K, V> where K : IHashable
{
Maybe<V> Get(K key);
bool Add(K key, V value);
}
Уявіть, якби замість цього у вас було таке:
class HashTable
{
object Get(IHashable key);
bool Add(IHashable key, object value);
}
Що б ти зробив з value
тобою, коли ви виходили звідти? З цим нічого не можна зробити, ти лише знаєш, що це предмет. І якщо ви повторите це, все, що ви отримаєте, - це пара того, що ви знаєте, - це IHashable
(що не дуже допомагає вам, оскільки воно має лише одне властивість Hash
), а те, що ви знаєте, - це object
(що допомагає вам ще менше).
Або щось на основі вашого прикладу:
class Repository<T> where T : ISerializable
{
T Get(int id);
void Save(T obj);
void Delete(T obj);
}
Елемент повинен бути серіалізаційним, оскільки він буде зберігатися на диску. Але що робити, якщо у вас це є:
class Repository
{
ISerializable Get(int id);
void Save(ISerializable obj);
void Delete(ISerializable obj);
}
З родовим випадку, якщо ви поставите BankAccount
, ви отримаєте BankAccount
назад, з методами і властивостями , як Owner
, AccountNumber
, Balance
, Deposit
,Withdraw
і т.д. Що - то ви можете працювати з. Тепер інший випадок? Ви кладете в , BankAccount
але ви отримаєте назад Serializable
, який має тільки одну властивість: AsString
. Що ти будеш робити з цим?
Також є кілька акуратних хитрощів, які можна зробити при обмеженому поліморфізмі:
F-обмежене кількісне визначення в основному там, де змінна типу знову з'являється в обмеженні. Це може бути корисно за деяких обставин. Наприклад, як ви пишете ICloneable
інтерфейс? Як ви пишете метод, коли тип повернення - це тип класу реалізації? Мовою з функцією MyType це легко:
interface ICloneable
{
public this Clone(); // syntax I invented for a MyType feature
}
Мовою з обмеженим поліморфізмом ви можете зробити щось подібне замість цього:
interface ICloneable<T> where T : ICloneable<T>
{
public T Clone();
}
class Foo : ICloneable<Foo>
{
public Foo Clone()
{
// …
}
}
Зауважте, що це не так безпечно, як версія MyType, тому що ніщо не заважає комусь просто передати "неправильний" клас конструктору типів:
class EvilBar : ICloneable<SomethingTotallyUnrelatedToBar>
{
public SomethingTotallyUnrelatedToBar Clone()
{
// …
}
}
Члени абстрактного типу
Як виявляється, якщо у вас є члени абстрактних типів і підтипів, ви насправді можете пройти повністю без параметричного поліморфізму і все одно робити все те ж саме. Scala прямує в цьому напрямку, будучи першою основною мовою, яка почалася з генерики, а потім намагається їх видалити, а саме навпаки, наприклад, Java та C #.
В основному, у Scala, як і ви можете мати поля та властивості та методи як членів, ви також можете мати типи. І так само, як і поля, і властивості, і методи можуть бути залишені абстрактними, щоб потім бути реалізованими в підкласі, і члени типу можуть бути залишені абстрактними. Повернемося до колекції, простої List
, яка виглядала б приблизно так, якби вона підтримувалася в 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>
. це, очевидно, реальна програма життя. приклад чудовий. але я чомусь не відчуваю себе задоволеним. я радше хочу замислитись, коли це доречно, а коли ні. відповіді тут мають певний внесок у цей процес, але я все ще відчуваю себе неповноцінним навколо цього всього. це дивно, тому що проблеми на рівні мови мене вже давно не турбують.