Можливо, спочатку корисно розрізнити тип і клас, а потім зануритися в різницю між підтипом і підкласом.
У решті цієї відповіді я припускаю, що обговорювані типи є статичними типами (оскільки субтипізація зазвичай виникає в статичному контексті).
Я збираюся розробити іграшковий псевдокод, щоб допомогти проілюструвати різницю між типом і класом, оскільки більшість мов їх поєднують хоча б частково (з причини, яку я коротко торкнуся).
Почнемо з типу. Тип - це мітка для виразу у вашому коді. Значення цієї мітки та відповідність (для певного типу визначення системи, яке відповідає специфічній системі), для всіх інших значень міток можна визначити зовнішньою програмою (контролером типу) без запуску програми. Ось що робить ці етикетки особливими та заслуговуючими на власне ім’я.
Нашою мовою іграшок ми можемо дозволити створення таких етикеток.
declare type Int
declare type String
Тоді ми можемо позначити різні значення як такі, що є.
0 is of type Int
1 is of type Int
-1 is of type Int
...
"" is of type String
"a" is of type String
"b" is of type String
...
Завдяки цим твердженням наш інструмент перевірки типу може тепер відхиляти такі заяви, як
0 is of type String
якщо однією з вимог нашої системи типів є те, що кожен вираз має унікальний тип.
Нехай покинемо осторонь, наскільки це незграбно, і як у вас виникнуть проблеми із призначенням нескінченної кількості типів виразів. Ми можемо повернутися до цього пізніше.
Клас, з іншого боку, - це сукупність методів і полів, згрупованих разом (можливо, з модифікаторами доступу, такими як приватні чи загальнодоступні).
class StringClass:
defMethod concatenate(otherString): ...
defField size: ...
Екземпляр цього класу отримує можливість або створювати, або використовувати попередні визначення цих методів і полів.
Ми можемо вибрати асоціювати клас з таким типом, щоб кожен екземпляр класу автоматично мічувався цим типом.
associate StringClass with String
Але не кожному типу потрібно мати асоційований клас.
# Hmm... Doesn't look like there's a class for Int
Можливо також, що в мові іграшок не кожен клас має тип, особливо якщо не всі наші вирази мають типи. Трохи складніше (але не неможливо) уявити, як виглядатимуть правила узгодженості системи типу, якби деякі вирази мали типи, а деякі ні.
Більше того, в нашій іграшковій мові ці асоціації не повинні бути унікальними. Ми могли б пов’язати два класи одного типу.
associate MyCustomStringClass with String
Тепер майте на увазі, що для нашої перевірки типу даних немає вимоги відслідковувати значення виразу (а в більшості випадків це не робиться чи неможливо). Все, що він знає, - це етикетки, які ви їм сказали. Нагадуючи, раніше перевіряючий текст зміг відхилити заяву лише 0 is of type String
через наше штучно створене правило типу, що вирази повинні мати унікальні типи, і ми вже позначили цей вираз 0
чимось іншим. У неї не було особливих знань про значення 0
.
То як щодо підтипу? Добре підтипізація - це назва загального правила перевірки набору тексту, яке розслаблює інші правила, які ви можете мати. А саме, якщо A is subtype of B
тоді скрізь ваш машинник вимагає етикетки B
, він також приймає A
.
Наприклад, ми можемо зробити наступне для своїх номерів замість того, що було раніше.
declare type NaturalNum
declare type Int
NaturalNum is subtype of Int
0 is of type NaturalNum
1 is of type NaturalNum
-1 is of type Int
...
Підкласифікація - це скорочення для оголошення нового класу, що дозволяє повторно використовувати раніше заявлені методи та поля.
class ExtendedStringClass is subclass of StringClass:
# We get concatenate and size for free!
def addQuestionMark: ...
Нам не доводиться пов'язувати екземпляри з ExtendedStringClass
тим, String
як ми робили, StringClass
оскільки, нарешті, це зовсім новий клас, нам просто не довелося писати стільки. Це дозволило б нам надати ExtendedStringClass
тип, несумісний з String
точкою зору перевірки типу.
Так само ми могли вирішити зробити цілком новий клас NewClass
і зробили це
associate NewClass with String
Тепер кожен примірник StringClass
можна замінити з NewClass
точки зору перевірки шрифтів.
Тож у теорії субтипування та підкласи - це абсолютно різні речі. Але жодна мова, про яку я знаю, що має типи та класи, насправді не робить так. Почнемо розбиратися в нашій мові та пояснити обґрунтування деяких наших рішень.
По-перше, навіть якщо теоретично абсолютно різним класам можна давати один і той же тип або ж класу можна надати той же тип, що і значення, які не є примірниками жодного класу, це сильно заважає корисності контролера типу. Засіб перевірки набору ефективно позбавлене можливості перевірити, чи існує метод чи поле, яке ви викликаєте в виразі, на цьому значенні, і це, мабуть, перевірка, яку ви хотіли б, якщо ви зіткнетеся з проблемою грати разом із машинопис. Зрештою, хто знає, яке значення насправді знаходиться під цією String
міткою; це може бути щось, чого взагалі немає, наприклад, concatenate
метод!
Добре, тож давайте встановимо, що кожен клас автоматично генерує новий тип з тим самим іменем, що і цей клас, і associate
екземпляри з цим типом. Це дозволяє нам позбутися associate
, а також різних назв між StringClass
і String
.
З тієї ж причини, ймовірно, ми хочемо автоматично встановити зв'язок підтипу між типами двох класів, де один є підкласом іншого. Адже гарантовано, що всі підкласи мають усі методи та поля, які має батьківський клас, але навпаки не вірно. Тому, хоча підклас може передавати будь-коли, коли вам потрібен тип батьківського класу, тип батьківського класу слід відхилити, якщо вам потрібен тип підкласу.
Якщо ви поєднаєте це з умовою, що всі визначені користувачем значення повинні бути екземплярами класу, то ви можете is subclass of
витягнути подвійний обов'язок і позбутися is subtype of
.
І це приводить нас до характеристик, якими володіє більшість популярних статично типових мов ОО. Є набір "примітивних" типів (наприклад int
, float
тощо), які не пов'язані з жодним класом і не визначені користувачем. Тоді у вас є всі визначені користувачем класи, які автоматично мають типи однойменних ідентифікують підкласи з підтипом.
Заключне зауваження, яке я зроблю, полягає в химерності декларування типів окремо від значень. Більшість мов пов'язують створення двох, так що декларація типу також є заявою для генерування абсолютно нових значень, які автоматично позначаються цим типом. Наприклад, декларація класу зазвичай і створює тип, і спосіб інстанціювання значень цього типу. Це позбавляється від деякої незграбності і, за наявності конструкторів, також дозволяє створювати мітку нескінченно багато значень з типом за один штрих.