НОУ ІНТУІТ, Лекція, Спадкування та поліморфізм
Поліморфізм
Акумулювання компонентів є єдиною метою успадкування. Ми вже демонстрували цю можливість, коли писали клас PREVIEW як спадкоємця TOURISM. Ми хочемо отримати переваги від успадкування не просто за рахунок модульності, а за рахунок того, що воно задає відносини між типами, — дельфіни є ссавцями, кільця є групами.
Переходячи до програмістського прикладу: з того, що "будь-яке таксі є транспортним засобом", слід можливість присвоювання між змінними і виразами цих двох типів. Нехай оголошено змінні:
Структура успадкування, розглянута вище, робить припустимим присвоєння:
Воно відрізняється від присвоєння, яке нам зустрічалося досі. Раніше вираз джерела присвоєння і змінна , що ставить за мету привласнення, були одного типу, але тепер вони різні. Специфіка у цьому, що тип джерела є нащадком типу мети.
У ефекті присвоєння немає нічого нового. Все, що ми вивчили про присвоєння, зберігається, якщо не брати до уваги змін у типі об'єктів. Припускаючи, що обидві змінні спочатку приєднані до об'єктів, і картинка, що відображає ефект "до" після присвоєння, повністю зберігається:

Це звичайне присвоювання посилань. Самі об'єкти – тут об'єкти типів TAXI та VEHICLE – не змінюються. Новинка в тому, що після надання змінна типу VEHICLE тепер може бути приєднана до об'єкта типу TAXI — до об'єкта одного зі своїх нащадків.
Визначення
Нам потрібна відповідна для цієї ситуації термінологія.
Визначення: поліморфізм
Приєднання(присвоєння або передача аргументу) є поліморфним, якщо цільова змінна тавираз джерела мають різні типи.
Сутністьабовиразє поліморфним, якщо - як результат поліморфного приєднання - вона може під час виконання бути приєднана до об'єктів різних типів.
Контейнерна структура даних є поліморфною, якщо вона може містити посилання на різні типи об'єктів.
Нагадую, що "сутності" включають змінні (атрибути, локальні змінні), а також формальні аргументи методів та Result.
"Поліморфізм" є основою цих можливостей. Його часто плутають з динамічним зв'язуванням, що вивчається нижче. Динамічне зв'язування необхідне реалізації поліморфізму, але це різні концепції.
Як зазначалося у визначенні, поліморфізм існує як при присвоєння, а й передачі аргументу на момент виклику методу. Нехай деякий довільний клас, наприклад, DAILYSCHEDULE має метод:
Тоді виклик цього є цілком коректним:
Поліморфізм – це не трансформація
Незважаючи на своє ім'я - від грецького словосполучення "множинність форм" - поліморфізм не є причиною зміни під час виконання об'єктом своєї "форми" (зміною типу). Поліморфне приєднання застосовується тільки до типів посилань з ефектом, показаним на останньому малюнку, - змінюються посилання, але самі об'єкти не змінюють тип.
Іноді виникає необхідність трансформації об'єкта, але це не пов'язано з поліморфізмом. Найпростішим прикладом такої ситуації є привласнення цілої цільової змінної типу REAL , чиє внутрішнє уявлення відрізняється від уявлення джерела. Придатним механізмом у такому разі є трансформація , але не поліморфне приєднання.
Ми не будемо більше зупинятися на механізмахперетворення. Якщо необхідно його використовувати, то можна проаналізувати для початку клас REAL32 в EiffelBase (дивись пропозиціюconvert), цього буде достатньо для розуміння основних ідей трансформації.
Що ж до нашого обговорення, слід розуміти, що трансформація і поліморфізм є взаємовиключними механізмами: якщо застосовується одне із них, то другий не застосовується. Отже, коли ви бачите привласнення a:=b або коли йдеться про передачу аргументу під час виклику методу, то ніколи не виникає невизначеність, з контексту завжди ясно, з яким випадком ми маємо справу.
Поліморфні структури даних
Особливий інтерес представляє останній випадок у визначенні поліморфізму - поліморфна структура даних, яка також називається поліморфним контейнером. Розглянемо типовий контейнер – список, призначений для зберігання транспортних засобів:
У випадку з fleet фактичним родовим параметром, що відповідає G , є тип VEHICLE , так що виклик чекає аргументу типу VEHICLE , як у виклику feet.end (myvehicle)
Але поліморфізм грає свою роль, і будь-який тип, що є нащадком VEHICLE, може чудово використовуватись. Тому поряд з можливістю застосування myvehicle цілком коректно використовувати виклик:
У випадку, аргумент може бути будь-якого типу, що є нащадком VEHICLE , таким як TAXI, TRAM та інші.
Поліморфний контейнер є результатом послідовності таких вставок з можливістю різних фактичних типів у кожному випадку. Після декількох викликів extend наш список fleet може виглядати, наприклад, так:
Список містить суміш об'єктів різних типів, усі є нащадками VEHICLE (включаючи BUS , не з'являвся раніше).
Можливість побудови такихполіморфних структур даних - результат комбінації двох фундаментальних ГО-механізмів, успадкування та універсальності. Це дає нам новий рівень гнучкості. Розглянемо, наприклад, запит last, результатом якого буде останній елемент списку. Він оголошений класі LIST[G] і повертає результат типу G . Сутність fleet оголошена як fleet: LIST [VEHICLE] , використовуючи VEHICLE як фактичний родовий параметр для G . Тому вираз:
Я можу почути від вас: це несправедливо! Просто погляньте на останній малюнок, адже останній об'єкт – таксі. Чому ж я не можу виконати операцію, цілком припустиму для цього об'єкта?
По-перше, життя сповнене несправедливостей, і потрібно вміти приймати його таким, яким воно є.
По-друге, як ви можете бути впевненим, що повернутий об'єкт дійсно таксі? Малюнок - це просто малюнок, немає жодних гарантій, що при наступному виконанні перебуватиме наприкінці списку fleet.
І третє: все буде гаразд — і це справжня відповідь! Існують способи перевірки того, чим є отриманий об'єкт, чи є він таксі в даному конкретному виконанні, і якщо так, то виклик take можна зробити законним. Але перш ніж дізнатися, як це робиться, доведеться прочитати ще кілька десятків сторінок. Хіба я не казав вам, що життя сповнене несправедливостей?