Переваги ООП

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

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

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

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

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

Поліморфізм виявляється корисним переважно в наступних ситуаціях.

  • Обробка різнорідних структур даних. Програми можуть працювати, не розрізняючи виду об'єктів, що істотно спрощує код. Нові види можуть бути додані в будь-який момент.
  • Зміна поведінки під час виконання. На етапі виконання один об'єкт може бути замінений іншим, що дозволяє легко, без зміни коду, адаптувати алгоритм у залежності від того, який використовується об'єкт.
  • Реалізація роботи із спадкоємцями. Алгоритми можна узагальнити настільки, що вони вже зможуть працювати більш ніж з одним видом об'єктів.
  • Створення "каркасу" (framework). Незалежні від програми частини предметної області можуть бути реалізовані у вигляді набору універсальних класів, або каркаса (framework), і надалі розширені за рахунок додавання частин, специфічних для конкретного застосування.

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

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

Недоліки ООП

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

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

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

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

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

1. Неефективність на етапі виконання. У мовах типу Smalltalk повідомлення інтерпретуються під час виконання програми шляхом здійснення їх пошуку в одній або кількох таблицях і за рахунок вибору відповідного методу. Звичайно, це повільний процес. І навіть при використанні найкращих методів оптимізації Smalltalk-програми в десять разів повільніше оптимізованих C-програм.У гібридних мовах типу Oberon-2, Object Pascal і C + + відправка повідомлення призводить лише до виклику через покажчик процедурної змінної. На деяких машинах повідомлення виконуються лише на 10% повільніше, ніж звичайні процедурні виклики. І оскільки повідомлення зустрічаються в програмі набагато рідше за інших операцій, їх вплив на час виконання впливу практично не робить.Однак існує інший фактор, який впливає на час виконання: інкапсуляція даних. Рекомендується не надавати прямий доступ до полів класу, а виконувати кожну операцію над даними через методи. Така схема приводить до необхідності виконання процедурного виклику кожен раз при доступі до даних. Однак якщо інкапсуляція використовується тільки там, де вона необхідна (тобто в тих випадках, коли це стає перевагою), то уповільнення цілком прийнятне.

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

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

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

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

Лекція 12. Види відношень між класами, асоціація класів, наслідування.

 

Типи відносин між класами

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

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

  • агрегація (Aggregation);
  • асоціація (Association);
  • успадкування (Inheritance);
  • метаклас (Metaclass).

Агрегація

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

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

У нашому прикладі ставлення contain є двонаправленим. Об'єкт класу Aquarium містить кілька об'єктів Fish. У той же час кожна рибка "знає", в якому саме акваріумі вона живе. Кожен клас має свою роль в агрегації, яка вказує, яке місце займає клас у даному відношенні. Ім'я ролі не є обов'язковим елементом позначень і може бути відсутнім на діаграмі. У прикладі можна бачити роль home класу Aquarium (акваріум є домом для рибок), а також роль inhabitants класу Fish (рибки є мешканцями акваріума). Назва ролі звичайно збігається з назвою відповідного поля в класі. Зображення такого поля на діаграмі зайве, якщо вже вказано ім'я ролі. Тобто в даному випадку клас Aquarium матиме властивість (поле) inhabitants, а клас Fish - властивість home.

Кількість об'єктів, які беруть участь у відношенні, записується поряд з ім'ям ролі. Запис "0 .. n" означає "від нуля до нескінченності". Прийнято також позначення:

"1 .. n" - від одиниці до нескінченності;

"0" - нуль;

"1"- один;

"N" - фіксована кількість;

"0 .. 1" - нуль чи один.

Код, що описує розглянуту модель і явище агрегації, може виглядати, наприклад, наступним чином:

// Визначення класу Fishpublic class Fish { // Визначення поля home // (Посилання на об'єкт Aquarium) private Aquarium home; public Fish () { }}// Визначення класу Aquariumpublic class Aquarium { // Визначення поля inhabitants // (Масив посилань на об'єкти Fish) private Fish inhabitants []; public Aquarium () { }}

Асоціація

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

Як приклад можна розглянути програміста і його комп'ютер. Між цими двома об'єктами немає агрегації, але існує чіткий взаємозв'язок. Так, завжди можна встановити, за якими комп'ютерами працює будь-якої програміст, а також які люди користуються окремо взятим комп'ютером. У розглянутому прикладі має місце асоціація "багато-до-багатьох".

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

Код, відповідний розглянутому наприклад, буде, наприклад, таким:

public class Programmer { private Computer computers []; public Programmer () { }}public class Computer { private Programmer programmers []; public Computer () { }}