Чому основні сильні статичні мови OOP заважають успадковувати примітиви?


53

Чому це нормально і в основному очікується:

abstract type Shape
{
   abstract number Area();
}

concrete type Triangle : Shape
{
   concrete number Area()
   {
      //...
   }
}

... поки це не нормально, і ніхто не скаржиться:

concrete type Name : string
{
}

concrete type Index : int
{
}

concrete type Quantity : int
{
}

Моя мотивація - максимальне використання системи типу для перевірки правильності компіляції.

PS: так, я прочитав це, і обгортання - це хитра робота.


1
Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
maple_shaft

У мене було подібну мотивацію в цьому питанні , вам може бути цікаво.
default.kramer

Я збирався додати відповідь, що підтверджує ідею "ти не хочеш успадкування", і що обгортання є дуже потужним, включаючи надання того, що потрібно від неявного чи явного кастингу (або відмов), особливо з оптимізаціями JIT, що дозволяють запропонувати вам так чи інакше отримайте майже таку ж ефективність, але ви пов’язали цю відповідь :-) Я б тільки додав, було б добре, якби мови додали функції, щоб зменшити код коробки, необхідний для властивостей / методів переадресації, особливо якщо є лише одне значення.
Марк Херд

Відповіді:


82

Я припускаю, що ви думаєте про такі мови, як Java та C #?

У цих мовах примітиви (як int) є в основному компромісом для роботи. Вони підтримують не всі функції об’єктів, але вони швидші та з меншими накладними витратами.

Для того, щоб об'єкти підтримували успадкування, кожному екземпляру потрібно "знати" під час виконання, для якого класу це екземпляр. Інакше замінені методи не можуть бути вирішені під час виконання. Для об'єктів це означає, що дані екземпляра зберігаються в пам'яті разом із вказівником на об’єкт класу. Якщо таку інформацію також слід зберігати разом з примітивними значеннями, вимоги до пам'яті будуть балонними. 16-бітне ціле значення потребує своїх 16 біт для значення та додатково 32 або 64 бітової пам'яті для вказівника на його клас.

Крім витрат на пам'ять, ви також можете очікувати, що ви зможете змінити звичайні операції на примітивах, таких як арифметичні оператори. Без субтипування такі оператори +можуть бути складені до простої інструкції машинного коду. Якщо це може бути відмінено, вам потрібно буде вирішити методи під час виконання, набагато більш дорогі операції. (Можливо, ви знаєте, що C # підтримує перевантаження оператора - але це не те саме. Перевантаження оператора вирішується під час компіляції, тому не існує штрафу за виконання за замовчуванням.)

Струни не є примітивними, але вони все ще "особливі" у тому, як вони представлені в пам'яті. Наприклад, вони "інтерновані", що означає, що два літеральні рядки, які є рівними, можуть бути оптимізовані до одного посилання. Це було б неможливо (або, принаймні, набагато менш ефективно), якщо рядкові екземпляри також слідкуватимуть за класом.

Те, що ви описуєте, безумовно, було б корисним, але його підтримка вимагатиме накладних витрат на кожне використання примітивів і рядків, навіть коли вони не користуються спадщиною.

Мова Smalltalk робить (я вважаю) дозволяють підкласи цілих чисел. Але коли розроблялася Java, Smalltalk вважався занадто повільним, і накладні витрати на те, щоб все було об'єктом, вважалися однією з головних причин. Ява пожертвував деякою елегантністю та концептуальною чистотою, щоб отримати кращі показники.


12
@Den: stringзапечатаний, оскільки він призначений для того, щоб поводитися непорушно. Якщо хтось міг успадкувати рядок, можна було б створити змінні рядки, що зробило б його справді схильним до помилок. Тони коду, включаючи саму структуру .NET, покладаються на рядки, що не мають побічних ефектів. Дивіться також тут, розповідає вам те саме: quora.com/Why-String-class-in-C-is-a-sealed-class
Doc Brown

5
@DocBrown Це також причина String, позначена і finalв Java.
Дев

47
"Коли Java розроблялася, Smalltalk вважався занадто повільним [...]. Java жертвувала деякою елегантністю та концептуальною чистотою, щоб отримати кращі результати". - За іронією долі, звичайно, Java насправді не набрала цієї продуктивності, поки Sun не придбала компанію Smalltalk, щоб отримати доступ до технології Smalltalk VM, оскільки власний JVM від Sun був повільним і випустив HotSpot JVM, трохи модифікований Smalltalk VM.
Йорг W Міттаг

3
@underscore_d: У відповіді, з якою ви посилаєтесь, явно сказано, що C♯ не має примітивних типів. Звичайно, деяка платформа, для якої існує реалізація C♯, може мати або не мати примітивні типи, але це не означає, що C♯ має примітивні типи. Наприклад, існує реалізація Ruby для CLI, і CLI має примітивні типи, але це не означає, що Ruby має примітивні типи. Реалізація може або не може обрати для реалізації типи значень, зіставивши їх на примітивні типи платформи, але це приватна внутрішня деталь реалізації, а не частина специфікації.
Йорг W Міттаг

10
Вся справа в абстракції. Ми повинні тримати голову чистою, інакше ми закінчимося дурницями. Наприклад: C♯ реалізований у .NET. .NET реалізований на Windows NT. Windows NT реалізований на x86. x86 реалізований на силіконовому діоксиді. SiO₂ - це просто пісок. Отже, a stringна C♯ - це просто пісок? Ні, звичайно, ні, а stringна C♯ - це те, про що говорить специфіка C♯. Те, як воно реалізується, не має значення. Власна реалізація C♯ реалізовуватиме рядки як масиви байтів, реалізація ECMAScript позначатиме їх на ECMAScript Strings тощо.
Jörg W Mittag

20

Те, що деякі мови пропонують, це не підкласифікація, а підтипу . Наприклад, Ada дозволяє створювати похідні типи або підтипи . У розділі Система програмування / типу Ada варто прочитати, щоб зрозуміти всі деталі. Ви можете обмежити діапазон значень, що саме ви хочете більшу частину часу:

 type Angle is range -10 .. 10;
 type Hours is range 0 .. 23; 

Ви можете використовувати обидва типи як цілі, якщо явно перетворити їх. Зауважте також, що ви не можете використовувати один замість іншого, навіть коли діапазони структурно еквівалентні (типи перевіряються іменами).

 type Reference is Integer;
 type Count is Integer;

Вищезазначені типи несумісні, хоча вони представляють однаковий діапазон значень.

(Але ви можете використовувати Uncont_Conversion; не кажіть людям, про які я вам казав)


2
Власне, я думаю, що мова йде більше про семантику. Використання кількості, де очікується індекс, тоді, сподіваємось, призведе до помилки часу компіляції
Marjan Venema

@MarjanVenema Це так, і це робиться навмисно, щоб ловити логічні помилки.
coredump

Моя думка полягала в тому, що не у всіх випадках, коли вам потрібна семантика, вам знадобляться діапазони. Тоді ви мали би те, type Index is -MAXINT..MAXINT;що якось нічого не робить для мене, як усі цілі числа були б дійсними? Отже, яку помилку я отримаю при передачі кута до індексу, якщо все, що перевіряється, - це діапазони?
Мар'ян Венема

1
@MarjanVenema У другому прикладі обидва типи є підтипами Integer. Однак якщо ви оголошуєте функцію, яка приймає Count, ви не можете пройти посилання, оскільки перевірка типу заснована на еквівалентності імен , що є протилежним "всім, що перевіряється, це діапазони". Це не обмежується цілими числами, ви можете використовувати перелічені типи чи записи. ( archive.adaic.com/standards/83rat/html/ratl-04-03.html )
coredump

1
@Marjan Один прекрасний приклад того, чому типи тегів можуть бути досить потужними, можна знайти в серії Еріка Ліпперта щодо впровадження Zorg в OCaml . Це дозволяє компілятору ловити велику кількість помилок - з іншого боку, якщо ви дозволяєте неявно перетворювати типи, це, здається, робить цю функцію марною. оскільки вони мають один і той же базовий тип.
Voo

16

Я думаю, це може бути питанням X / Y. Помітні бали, з питання ...

Моя мотивація - максимальне використання системи типу для перевірки правильності компіляції.

... і з вашого коментаря, що розробив:

Я не хочу мати можливість неявно замінювати одне іншим.

Вибачте, якщо я щось пропускаю, але ... Якщо це ваші цілі, то чому на Землі ви говорите про спадщину? Неявна замінюваність - це ... як ... вся її справа. Знаєте, Принцип заміни Ліскова?

Те, що вам здається, насправді - це концепція "сильного типу", завдяки чому щось "є", наприклад, intу діапазоні та репрезентації, але не може бути замінено контекстами, які очікують intі навпаки. Я б запропонував шукати інформацію про цей термін і незалежно від того, якою мовою ви обрали б вашу мову. Знову ж таки, це практично буквально протилежно спадку.

А для тих, кому не сподобається відповідь X / Y, я думаю, що заголовок все ще може відповідати посиланням на LSP. Первісні типи примітивні, тому що вони роблять щось дуже просте, і це все, що вони роблять . Дозволити їх успадковувати і тим самим робити нескінченними можливі наслідки, це призведе до великого здивування в кращому випадку і фатального порушення ЛСП в гіршому випадку. Якщо я можу з оптимізмом припустити, що Фалес Перейра не заперечує, цитую цей феноменальний коментар:

Існує додаткова проблема, що якби хтось зміг успадкувати від Int, у вас був би невинний код типу "int x = y + 2" (де Y - похідний клас), який тепер записує журнал у базу даних, відкриває URL-адресу та якось воскресити Елвіса. Примітивні типи повинні бути безпечними і з більш-менш гарантованою, чітко визначеною поведінкою.

Якщо хтось бачить примітивний тип, здоровою мовою, він справедливо припускає, що він завжди буде робити свою одну дрібницю, дуже добре, без сюрпризів. У примітивних типів відсутні доступні декларації класів, які б сигналізували про те, чи вони можуть бути, чи не можуть бути успадковані та чи їх методи перекрито. Якби вони були, це справді було б дуже дивно (і повністю зруйнувало зворотну сумісність, але я знаю, що це відповідь на зворотній бік "чому X не був розроблений з Y").

... хоча, як зауважив Муонг Дак у відповідь, мови, що дозволяють перезавантажувати оператора, дозволяють користувачеві плутати себе в подібній чи однаковій мірі, якщо вони дійсно хочуть, тому сумнівно, чи справедливий цей останній аргумент. І я зараз перестану підсумовувати коментарі інших людей, хе.


4

Для того, щоб дозволити успадкування за допомогою віртуальної диспетчеризації 8, що часто вважається досить бажаним у розробці додатків), потрібна інформація про тип виконання. Для кожного об'єкта необхідно зберігати деякі дані щодо типу об'єкта. Примітиві, за визначенням, не мають цієї інформації.

Є дві (керовані, запущені на VM) основні мови OOP, які мають примітиви: C # та Java. Багато інших мов не мають примітивів в першу чергу, або використовують подібні міркування, щоб дозволити / використовувати їх.

Примітиви - компроміс для продуктивності. Для кожного об'єкта вам потрібен простір для його заголовка об’єкта (у Java, як правило, 2 * 8 байт на 64-бітних VM), плюс його поля, плюс можливе заміщення (У точці доступу кожен об'єкт займає кількість байтів, кратне кількості 8). Таким чином, intяк об'єкт потрібно мати щонайменше 24 байти пам'яті, а не лише 4 байти (на Java).

Таким чином, для підвищення продуктивності додавали примітивні типи. Вони полегшують цілу масу речей. Що a + bозначає, якщо обидва є підтипами int? Щоб вибрати правильне доповнення, потрібно додати певний вид зневаги. Це означає віртуальну розсилку. Наявність можливості використовувати дуже простий код для додавання набагато, набагато швидше і дозволяє проводити оптимізацію часу компіляції.

Stringінший випадок. І в Java, і в C #, Stringє об'єктом. Але в C # його запечатано, а в Java - його фінал. Це тому, що і стандартні бібліотеки Java, і C # вимагають Stringнезмінності s, і підкласифікація їх порушить цю незмінність.

У випадку з Java, VM може (і робить) інтернинг Strings та "об'єднати" їх, що забезпечує кращу продуктивність. Це працює лише тоді, коли струни справді незмінні.

Крім того, рідко потрібно підкласи примітивних типів. Поки примітивів не можна підкласифікувати, існує ціла маса акуратних речей, які математика розповідає нам про них. Наприклад, ми можемо бути впевнені, що додавання є комутативним та асоціативним. Ось що нам підказує математичне визначення цілих чисел. Крім того, у багатьох випадках ми можемо легко перевернути інваріанти над петлями за допомогою індукції. Якщо ми дозволимо підкласифікувати int, ми втрачаємо ті інструменти, які нам надає математика, тому що ми більше не можемо гарантувати, що певні властивості зберігають. Таким чином, я б сказав, що здатність не в змозі підкласити примітивні типи насправді є хорошою справою. Менше речей хтось може зламати, плюс компілятор може часто доводити, що йому дозволено робити певні оптимізації.


1
Ця відповідь - це "вуса" ... вузька. to allow inheritance, one needs runtime type information.Помилковий. For every object, some data regarding the type of the object has to be stored.Помилковий. There are two mainstream OOP languages that feature primitives: C# and Java.Що, C ++ зараз не є мейнстрімом? Я буду використовувати його як своє спростування, оскільки інформація про тип виконання - це термін C ++. Це абсолютно не потрібно, якщо ви не використовуєте dynamic_castабо typeid. І навіть якщо RTTI увімкнено, успадкування займає лише місце, якщо в класі є virtualметоди, на які вказується таблиця методів для класів, наприклад
underscore_d

1
Спадкування в C ++ працює набагато інакше, ніж у мовах, що працюють на VM. віртуальна відправка вимагає RTTI, що не було частиною C ++. Спадкування без віртуальної відправки дуже обмежене, і я навіть не впевнений, чи варто порівнювати це спадкування з віртуальним відправленням. Крім того, поняття "об'єкт" сильно відрізняється в C ++, ніж у C # або Java. Ви маєте рацію, є кілька речей, які я міг би сказати краще, але tbh потрапляння у всі досить задіяні моменти швидко призводить до необхідності написати книгу про мовний дизайн.
Полігном

3
Крім того, це не так, що "віртуальна відправка вимагає RTTI" в C ++. Знову лише, dynamic_castі цього typeinfoвимагають. Віртуальна відправка практично реалізується за допомогою вказівника на vtable для конкретного класу об'єкта, таким чином дозволяючи викликати правильні функції, але це не вимагає деталізації типу та відношення, притаманних RTTI. Все, що потрібно зробити компілятору, - це те, чи клас об'єкта є поліморфним, і якщо так, то що таке vptr примірника. Можна тривіально складати фактично відправлені класи -fno-rtti.
підкреслити_

2
Насправді навпаки, RTTI вимагає віртуальної відправки. Буквально -C ++ не дозволяє dynamic_castна заняттях без віртуальної відправки. Причина реалізації полягає в тому, що RTTI, як правило, реалізується як прихований член vtable.
MSalters

1
@MilesRout C ++ має все, що потрібна мові для OOP, принаймні дещо новіші стандарти. Можна стверджувати, що у старих стандартах C ++ відсутні деякі речі, необхідні для мови OOP, але навіть це розтягнення. C ++ не є мовою OOP високого рівня , оскільки дозволяє більш прямий, низький рівень контролю над деякими речами, але тим не менше дозволяє OOP. (Високий рівень / низький рівень тут в частині абстракції , інша мова, як керовані, абстрагує більше системи від C ++, отже, їх абстракція вища)
Полігном

4

У основних сильних статичних мовах OOP, субтипування розглядають насамперед як спосіб розширення типу та переопределення поточних методів типу.

Для цього "об'єкти" містять вказівник на їх тип. Це накладні витрати: код у методі, який використовує Shapeекземпляр, спочатку повинен отримати доступ до інформації про тип цього екземпляра, перш ніж він дізнається правильний Area()метод для виклику.

Примітив прагне дозволити лише операції на ньому, які можуть перекладати в єдині інструкції щодо машинної мови та не несуть з собою жодної інформації про тип. Зробити ціле число повільніше, щоб хтось міг підкласирувати, це було недостатньо привабливо, щоб зупинити будь-які мови, які зробили це, ставши мейнстрімом.

Тож відповідь на:

Чому основні сильні статичні мови OOP заважають успадковувати примітиви?

Є:

  • Попиту було мало
  • І це зробило б мову занадто повільною
  • Підтипізація розглядалася в першу чергу як спосіб розширення типу, а не як спосіб покращити перевірку статичного типу (визначений користувачем).

Однак ми починаємо отримувати мови, які дозволяють статичну перевірку на основі властивостей змінних, відмінних від 'type', наприклад, F # має "розмірність" та "одиницю", так що ви не можете, наприклад, додати довжину до області .

Існують також мови, які дозволяють "визначені користувачем типи", які не змінюють (або обмінюють) те, що робить тип, а просто допомагають перевірити статичний тип; див. відповідь coredump.


F # одиниці виміру - це приємна особливість, хоча, на жаль, невірно названа. Також це лише час компіляції, тому не надто корисний, наприклад, при споживанні зібраного пакету NuGet. Хоча в правильному напрямку.
День

Мабуть, цікаво відзначити, що "вимір" - це не "властивість, відмінна від" типу ", це просто більш багатий тип, ніж ви, можливо, звикли.
porglezomp

3

Я не впевнений, чи переглядаю я щось тут, але відповідь досить проста:

  1. Визначення примітивів таке: примітивні значення не є об'єктами, примітивні типи не є об'єктними типами, примітиви не є частиною об'єктної системи.
  2. Спадкування - особливість об’єктної системи.
  3. Ерго, примітиви не можуть брати участь у спадщині.

Зауважте, що насправді є лише дві сильні статичні мови OOP, які навіть мають примітиви, AFAIK: Java та C ++. (Насправді я навіть не впевнений у останньому. Я мало знаю про C ++, і те, що я виявив при пошуку, було заплутаним.)

У C ++ примітиви - це в основному спадщина, успадкована (призначена каламбуром) від C. Отже, вони не беруть участі в об'єктній системі (і, таким чином, успадкування), оскільки C не має ні об'єктної системи, ні успадкування.

На Яві примітиви - результат помилкової спроби покращити продуктивність. Примітиви - це також єдині типи значень у системі, насправді неможливо записати типи значень на Java, а об’єкти не можуть бути типовими типами. Отже, крім того, що примітиви не беруть участі в об'єктній системі, і тому ідея "успадкування" навіть не має сенсу, навіть якщо ви могли б успадкувати їх, ви б не змогли зберегти " цінність ». Це відрізняється , наприклад , від C♯ , який робить має значення типів ( structи), які тим не менше є об'єктами.

Інша справа, що неможливість успадкування насправді також не властива примітивам. У C♯, structs неявно успадковують System.Objectі можуть реалізувати interfaces, але вони не можуть ні успадковувати, ні успадковувати classes або structs. Також sealed classес не можна успадкувати від. У Java final classне можна успадкувати es.

tl; dr :

Чому основні сильні статичні мови OOP заважають успадковувати примітиви?

  1. примітиви не є частиною об'єктної системи (за визначенням, якби вони були, вони не були б примітивними), ідея успадкування прив’язана до об'єктної системи, ерго примітивне успадкування - це суперечність у термінах
  2. примітиви не є унікальними, безліч інших типів також не можуть бути успадковані ( finalабо sealedв Java або C♯, structs у C♯, case classes у Scala)

3
Е-е ... Я знаю, що це вимовляється "C Sharp", але, гм
Містер Лістер,

Думаю, ти досить помиляється на стороні C ++. Це зовсім не чиста мова ОО. Методи класу за замовчуванням не є virtual, це означає, що вони не підкоряються LSP. Напр., std::stringЦе не примітив, але він дуже поводиться як лише інша цінність. Така семантика значення є досить поширеною, вся частина STL C ++ передбачає її.
MSalters

2
"У Java примітиви - результат помилкової спроби покращити продуктивність". Думаю, ви не маєте поняття про масштабність хітів щодо виконання примітивів як типів об'єктів, що розширюються користувачем. Це рішення в яві є і обдуманим, і обґрунтованим. Уявіть собі, що вам потрібно виділити пам'ять на кожне intвикористання. Кожен розподіл набирає порядку 100 тонн плюс накладні витрати на вивезення сміття. Порівняйте це з одиничним циклом процесора, спожитим, додавши два примітивні ints. Ваші коди Java поповзають, якби дизайнери мови вирішили інше.
cmaster

1
@cmaster: у Scala немає примітивів, і її числова продуктивність точно така ж, як у Java. Тому що, ну, він компілює цілі числа в примітивні JVM int, тому вони виконують точно так само. (Народний Scala компілює їх у регістри примітивних машин; Scala.js компілює їх у примітивні ECMAScript Numbers.) У Ruby немає примітивів, але YARV і Rubinius компілюють цілі числа в цілі числа примітивних машин, JRuby компілює їх у JVM примітивні longs. Практично кожна реалізація Lisp, Smalltalk або Ruby використовує примітиви у віртуальній машині . Ось де оптимізація продуктивності…
Jörg W Mittag

1
… Належать: у компіляторі, а не в мові.
Йорг W Міттаг

2

Джошуа Блох в "Ефективній Java" рекомендує чітко проектувати спадщину або забороняти її. Примітивні класи не розроблені для успадкування, оскільки вони розроблені так, щоб вони були незмінні і дозволяючи успадкуванню могли змінити це в підкласах, таким чином, порушити принцип Ліскова, і це буде джерелом багатьох помилок.

У будь-якому випадку, чому це хакітне рішення? Вам слід віддати перевагу композиції над спадщиною. Якщо причина - ефективність, ніж у вас є точка, і відповідь на ваше запитання полягає в тому, що неможливо розмістити всі функції на Java, оскільки потрібно проаналізувати всі різні аспекти додавання функції. Наприклад, Java не мала Generics до 1,5.

Якщо у вас є багато терпіння, то вам пощастить, оскільки є план додати класи класів вартості на Java, що дозволить вам створити ваші класи цінностей, які допоможуть вам збільшити продуктивність, і в той же час це дасть вам більше гнучкості.


2

На абстрактному рівні ви можете включити все, що завгодно, мовою, яку ви розробляєте.

На рівні впровадження неминуче, що деякі з цих речей будуть простішими у виконанні, деякі складнішими, деякі можуть бути зроблені швидко, деякі можуть бути повільнішими тощо. Для цього, дизайнерам часто доводиться приймати важкі рішення та компроміси.

На рівні реалізації одним із найшвидших способів доступу до змінної є пошук її адреси та завантаження вмісту цієї адреси. У більшості процесорів є конкретні інструкції щодо завантаження даних з адрес, і ці інструкції, як правило, повинні знати, скільки байтів потрібно завантажити (один, два, чотири, вісім тощо) і куди потрібно розміщувати дані, які вони завантажують (єдиний реєстр, реєстрація пара, розширений регістр, інша пам'ять тощо). Знаючи розмір змінної, компілятор може точно знати, яку інструкцію випромінювати для використання цієї змінної. Не знаючи розміру змінної, компілятору потрібно було б вдатися до чогось складнішого і, мабуть, повільніше.

На абстрактному рівні точкою підтипу є можливість використовувати екземпляри одного типу, де очікується рівний або більш загальний тип. Іншими словами, може бути записаний код, який очікує об'єкт певного типу або щось більш похідне, не знаючи заздалегідь, що саме це буде. І зрозуміло, оскільки більш похідні типи можуть додавати більше членів даних, похідний тип не обов'язково має такі самі вимоги до пам'яті, як і його базові типи.

На рівні реалізації не існує простого способу для змінної заздалегідь визначеного розміру вмістити примірник невідомого розміру та отримати доступ до нього так, як зазвичай ви називаєте його ефективним. Але є спосіб трохи перемістити речі і використовувати змінну не для зберігання об'єкта, а для ідентифікації об'єкта та дозволення його зберігати десь в іншому місці. Таким чином є посилання (наприклад, адреса пам'яті) - додатковий рівень непрямості, який забезпечує зміну лише для того, щоб містити якусь інформацію фіксованого розміру, доки ми можемо знайти об'єкт за допомогою цієї інформації. Для цього нам просто потрібно завантажити адресу (фіксованого розміру), і тоді ми можемо працювати як завжди, використовуючи ті зсуви об'єкта, які ми знаємо, що є дійсними, навіть якщо цей об’єкт має більше даних при компенсаціях, які ми не знаємо. Ми можемо це зробити, тому що ми не '

На абстрактному рівні цей метод дозволяє зберігати (посилання на a) stringу objectзмінну, не втрачаючи інформацію, яка робить її a string. Це добре, щоб усі типи працювали так, і ви можете також сказати, що це багато в чому елегантно.

Однак на рівні впровадження додатковий рівень опосередкованості передбачає більше інструкцій, і для більшості архітектур це робить кожен доступ до об'єкта дещо повільнішим. Ви можете дозволити компілятору вичавити з програми більше продуктивності, якщо ви включите до своєї мови деякі типи, що часто використовуються, що не мають такого додаткового рівня непрямості (посилання). Але видаляючи цей рівень непрямості, компілятор більше не може дозволити вам підтипу безпечним способом пам’яті. Це тому, що якщо ви додасте більше членів даних до свого типу та призначите більш загальний тип, будь-які додаткові члени даних, які не вміщуються у просторі, призначеному для цільової змінної, будуть відрізані.


1

Загалом

Якщо клас абстрактний (метафора: поле з отвором (ими)), це нормально (навіть потрібно мати щось корисне!) "Заповнити отвір (и)", тому ми підкласи абстрактних класів.

Якщо клас конкретний (метафора: вікно повне), це не нормально змінювати існуючий, тому що якщо він повний, він повний. У нас немає місця, щоб додати щось більше всередині коробки, тому ми не повинні підкласити конкретні класи.

З примітивами

Примітиви - це конкретні класи за конструкцією. Вони представляють щось добре відоме, цілком визначене (я ніколи не бачив примітивного типу з чимось абстрактним, інакше це вже не примітив) і широко використовується через систему. Дозвіл підкласу примітивного типу та надання власної реалізації іншим, які покладаються на розроблену поведінку примітивів, може спричинити безліч побічних ефектів та величезних збитків!



Посилання - цікава думка щодо дизайну. Мені потрібно більше думати.
День

1

Зазвичай успадкування - це не семантика, яку ви хочете, тому що ви не можете замінити свій спеціальний тип ніде, де очікується примітив. Позичати з вашого прикладу Quantity + Indexнемає сенсу семантично, тому спадкові відносини - це неправильні відносини.

Однак у декількох мовах є поняття типу значення , яке виражає тип відносин, які ви описуєте. Скала - один із прикладів. Тип значення використовує примітив як основне подання, але має іншу ідентичність класу та операції зовні. Це впливає на поширення примітивного типу, але це скоріше композиція замість спадкових відносин.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.