Класи та наслідування

Матеріал з Вікі ЦДУ
Перейти до: навігація, пошук

Наслідування у класів - один з принципів ООП. Воно може бути використано для вираження генералізації або спеціалізації. Основна ідея в тому, що ви визначаєте новий тип, розширюючи або модифікуючи існуючий, іншими словами, похідний клас володіє всіма даними і методами базового класу, новими даними і методами і, можливо, модифікує деякі з існуючих методів. Різні мови використовують різні ОО жаргони для опису цього механізму (derivation, inheritance, sub-classing), для класу, від якого ви наслідуєте (базовий клас, батьківський клас, суперклас) і для класу (похідний клас, дочірній клас, підклас).

C++ (аналогічно для PHP): C++ використовує слова public, protected, і private для визначення типу наслідування і щоб заховати наслідувані методи або дані, роблячи їх приватними або захищеними. Хоча публічне наслідування найбільш часто використовується, за замовчуванням береться приватне. Як ми побачимо далі, C++ - підтримує множинне наслідування. Ось приклад синтаксису спадкування:

class Dog: public Animal 
{
 ... 
};

OP: Object Pascal при наслідуванні використовує не ключові слова, а спеціальний синтаксис, додаючи в дужках ім'я базового класу. Ця мова підтримує тільки один тип наслідування, який в C + + називається публічним. Як ми побачимо пізніше, класи OP походять від одного спільного базового класу:

type
Dog = class (Animal)
 ...
end;

Java: Java використовує слово extends для вираження єдиного типу наслідування, відповідного публічного наслідування в C + +. Java не підтримує множинне наслідування. Класи Java теж походять від загального базового класу.

class Dog extends Animal 
(
 ...
)

Примітка: Конструктори і ініціалізація базового класу. І в C++, і в Java у конструкторів успадковують класів складна структура. У Object Pascal за ініціалізацію базового об'єкту відповідає програміст. Це досить складний розділ, тому я пропустив його, хто бажає може ознайомитися з цим самостійно. Замість цього я зосереджуся на загальному базовому класі, множині спадкування, інтерфейсах, пізньому зв'язуванні та інших споріднених предметах.

Предок всіх класів

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

C++: Хоча мова C++ і не підтримує таку властивість, багато структур додатків базуються на ньому, вводячи ідею спільного базового класу. Приклад тому - MFC з його класом COobject. Фактично це мало великий сенс спочатку, коли мови не вистачало шаблонів.

OP: Кожен клас автоматично успадковує клас TObject. Так як мова не підтримує множинне спадкування, всі класи формують гігантське ієрархічне дерево. Клас TObject підтримує RTTI і володіє деякими іншими можливостями. Загальною практикою є використання цього класу, коли вам потрібно передати об'єкт невідомого типу.

Java: Як і в OP, всі класи беззастережно успадковують клас Object. І в цій мові у спільного класу теж є деякі обмежені властивості і невелика підтримка RTTI.

Доступ до методів базового класу

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

C + +: У C + + для вказівки потрібного класу можна використовувати оператор (::). Ви можете отримати доступ не тільки до методів базового класу, але до класів вище за ієрархією. Це дуже потужна техніка, але вона створює проблеми, коли ви додаєте в ієрархію проміжний клас.

OP: У Object Pascal для цієї мети є спеціальне слово inherited. Після цього слова ви можете написати ім'я методу базового класу або (в деяких випадках) просто використовувати це ключове слово для доступу до відповідного методу базового класу.

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

Совмістимість підтипів

Як я зазначав на початку, не всі ГО мови суворо типізовані, але всі три мови, на яких ми сконцентрувалися, володіють цією властивістю. В основному це означає, що об'єкти різних класів несумісні за типом. З цього правила є виняток: об'єкти похідних класів сумісні з типом їх базового класу. (Примітка: зворотне зазвичай невірно.)

C + +: У C + + правило сумісності підтипів справедливо тільки для покажчиків і посилань, але не для звичайних об'єктів. Фактично, у різних об'єктів різний розмір, тому їх не можна розташувати на тому ж місці в пам'яті.

OP: Завдяки посилальної-об'єктної моделі, сумісність підтипів можлива для кожного об'єкта. Більш того, всі об'єкти сумісні за типом з TObject.

Java: Java використовує ту ж модель, що і Object Pascal.

Примітка: Поліморфізм. Працює з підтипів особливо важлива для пізнього зв'язування та поліморфізму, як це показано в наступній секції.

Пізнє зв'язування (і поліморфізм)

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

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

OP: У Object Pascal пізніше зв'язування вводиться за допомогою ключових слів virtual і dynamic (різниця між ними тільки в оптимізації). У похідних класах перевизначення методи повинні бути відзначені словом override (це змушує компілятор перевіряти опис методу). Раціональне пояснення цій особливості OP полягає в тому, що дозволяється більше змін в базовому класі і надає деякий додатковий контроль під час компіляції.

Java: У Java всі методи використовують пізніше зв'язування, якщо ви не відзначите їх явно як final. Фінальні методи не можуть бути перевизначено і викликаються швидше. У Java написання методів з потрібною сигнатурою життєво важливо для забезпечення поліморфізму. Той факт, що в Java за замовчуванням використовується пізніше зв'язування, тоді як в C + + стандартом є раннє зв'язування, - явна ознака різного підходу цих двох мов: C + + часом жертвує ГО моделлю на користь ефективності, тоді як Java - навпаки

Примітка: Пізніше зв'язування для конструкторів та деструкторів. Object Pascal, на відміну від інших двох мов, дозволяє визначати віртуальні конструктори. Всі три мови підтримують віртуальні деструктори.

Абстрактні методи і класи

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

C + +: У C + + абстрактні методи або чисто віртуальні функції виходять додаванням так званого чистого описувач (= 0) у визначення методу. Абстрактні класи є просто класами з одним або більше абстрактним методом (або успадковують їх). Ви не можете створити об'єкт абстрактного класу.

OP: Object Pascal для виділення цих методів використовує ключове слово abstract. Крім того, абстрактними класами є класи, які мають або успадковують абстрактні методи. Ви можете створити об'єкт абстрактного класу (хоча компілятор видасть попередження). Це піддає ризику програму викликати абстрактний метод, що призведе до генерації помилки часу виконання і завершення програми.

Java: У Java і абстрактні методи, і абстрактні класи відзначаються ключовим словом abstract (дійсно, в Java обов'язково визначати як абстрактний клас, що має абстрактні методи, - хоча це здається деяким надмірністю). Похідні класи, які не перевизначають всі абстрактні методи, повинні бути позначені як абстрактні. Як і в C + +, не можна створювати об'єкти абстрактних класів.

Множинне наслідування та інтерфейси

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

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

Java: Java, як і Object Pascal, не підтримує множинне спадкування, але повністю підтримує інтерфейси. Методи інтерфейсів допускають поліморфізм, і Ви можете використовувати об'єкт, що здійснює інтерфейс, коли очікується інтерфейсний об'єкт. Клас може наслідувати або розширити один базовий клас, але може здійснити (це - ключове слово) численні інтерфейси. Хоча це не було сплановано заздалегідь, інтерфейси Java дуже добре вкладаються у модель COM.


RTTI

У суворо тіпізованних ГО мовах компілятор здійснює весь контроль типів, так що немає особливої необхідності зберігати інформацію про класи і типи в працюючій програмі. Тим не менш, є випадки (як, наприклад, динамічне перетворення типів), які вимагають інформацію про тип. З цієї причини всі три ГО мови, що розглядаються тут, більш-менш підтримують Ідентифікацію / Інформацію про тип Часу Виконання (RTTI).

C + +: спочатку не підтримував RTTI. Це було додано пізніше для динамічного перетворення типу (dynamic_cast) і зробило доступною деяку інформацію про тип для класів. Ви можете запитати ідентифікацію типу для об'єкта, і перевірити, чи належать два об'єкти одного класу.

OP: підтримує і вимагає багато RTTI. Доступний не тільки контроль відповідності та динамічне перетворення типів (за допомогою операторів is і as). Класи генерують розширену RTTI для своїх published властивостей, методів і полів. Фактично це ключове слово управляє частиною генерації RTTI. Вся ідея властивостей, механізм потоків (файли форм - DFM), і середовище Delphi, починаючи з Інспектора Об'єктів, сильно спирається на RTTI класів. У класу TObject є (крім інших) методи ClassName і ClassType. ClassType повертає змінну типу класу, об'єкт спеціального типу посилання на клас (який не є самим класом).

Java: як і в Object Pascal, в Java теж є єдиний базовий клас, який допомагає стежити за інформацією про клас. Безпечне перетворення типів (type-safe downcast) вбудовано в цю мову. Метод getClass () повертає свого роду метакласс (об'єкт класу, що описує класи), і Ви можете застосувати функцію getName () для того, щоб отримати рядок з ім'ям класу. Ви можете також використовувати оператор instanceof. Java включає в себе розширену RTTI для класів або інтроспекції, яка була введена для підтримки компонентної моделі JavaBeans. У Java існує можливість створювати класи під час виконання програми.


Обробка винятків

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

C + +: C + + використовує ключове слово throw для генерації виключення, try для позначки охороняється блоку і catch для запису коду обробки виключення. Виключення - об'єкти спеціального класу, які можуть утворювати деяку ієрархію у всіх трьох мовах. При виникненні виключення C + + виконує очищення стека до точки перехоплення виключення. Перед видаленням кожного об'єкта в стеці викликається відповідний деструктор.

OP: Object Pascal використовує подібні ключові слова: raise, try, і except і володіє подібними властивостями. Єдина істотна відмінність полягає в тому, що спустошення стека не проводиться, просто тому, що в стеці немає об'єктів. Крім того, ви можете додати в кінці блоку try слово finally, відзначаючи блок, який повинен виконуватися завжди, незалежно від того, було чи ні викликано виняток. В Delphi класи виключень - похідні Exception.

Java: Використовує ключові слова C + +, але веде себе як Object Pascal, включаючи додаткову ключове слово finally. (Це загальна властивість всіх мов з посилальної-об'єктною моделлю, вона була придбана Borland також і в C + + Builder 3.) Присутність алгоритму складання сміття обмежує використання finally в класі, який розподіляє інші ресурси, окрім пам'яті. Також Java суворіше вимагає, щоб всі функції, які можуть викликати виключення, описували у відповідному блоці, які виключення можуть бути викликані функцією. Ці описи винятків перевіряються компілятором, що є гарним властивістю, навіть якщо воно має на увазі деяку додаткову роботу для програміста. У класах Java об'єкти-винятку повинні наслідувати класу Throwable.


Шаблони (узагальнене програмування)

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

C + +: є шаблонні класи та функції, що відзначаються ключовим словом template. Стандартний C + + включає велику бібліотеку шаблонів, звану STL (Standart Template Library, Стандартна бібліотека шаблонів), яка підтримує специфічний і потужний стиль програмування: узагальнене програмування. C + + - єдиний з розглянутих трьох мов, який грунтується на підтримці узагальненого програмування, крім ООП.

OP: немає шаблонів. Контейнерні класи зазвичай будуються як контейнери об'єктів класу TObject, а потім уточнюються для необхідних об'єктів.

Java: реалізуються в рамках Generics (введеного в JDK 1.5 «Tiger»). Концептульно вони не відрізняються від шаблонів в C + +, але мають деякі особливості, які диктуються властивостями самої мови. Передбачені контейнери на всі випадки життя: List (зберігання послідовностей елементів), Map або асоціативні масиви (зв'язування одних об'єктів з іншими), Set (унікальність значень для кожного типу).

Інші специфічні властивості

Є ще інші властивості, не згадані мною, хоча вони важливі, тільки через те, що вони специфічні тільки для одного з трьох мов.

C + +: Я вже згадав множинне спадкування, віртуальні базові класи й шаблони. Ці властивості відсутні у двох інших ГО мовами. У C + + є ще перевантаження операторів, тоді як перевантаження методів присутня також в Java і була недавно додана в Object Pascal. C + + дозволяє програмістам перевантажувати і глобальні функції. Ви можете перевантажити оператори перетворення типів, написавши конвертує методи, які будуть викликатися «за лаштунками». Об'єктна модель C + + вимагає копіювати конструктори і перевантажувати оператори присвоювання, в чому не потребують інші дві мови, оскільки базуються на посилальної-об'єктної моделі.

Java: Тільки Java підтримує багатопоточність безпосередньо в мові. Об'єкти та методи підтримують механізм синхронізації (з ключовим словом synchronized): два синхронізованих методу одного класу не можуть виконуватися одночасно. Для створення нового потоку ви просто наслідуєте від класу Thread, перевантажуючи метод run (). Як альтернативу ви можете здійснити інтерфейс Runnable (що ви зазвичай робите в апплетах, що підтримують багатопоточність). Ми вже обговорювали прибиральник сміття. Ще одне ключове властивість Java, звичайно, ідея переносимого байтового коду, але це не стосується безпосередньо до мови. Інше примітне властивість - це підтримка заснованих на мові компонентів, відомих як JavaBeans і багато інших властивостей, нещодавно додані в цю мову.

OP: Ось деякі специфічні риси Object Pascal: посилання на класи, легкі для використання покажчики на методи (основа моделі обробки подій) і, зокрема, властивості (property). Властивість - це просто ім'я, яке приховує шлях, яким ви отримуєте доступ до даних або методом. Властивість може проектуватися на пряме читання або запис даних, а може посилатися на метод, що забезпечує доступ. Навіть якщо ви міняєте спосіб доступу до даних, вам не потрібно змінювати викликає код (хоча вам потрібно буде його перекомпілювати): це робить властивості дуже потужним засобом інкапсуляції.