Рекомендації майбутнім інтернам. ООП, яким воно має бути

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

article2

Інкапсуляція

Найбільша помилка в розумінні того, що таке інкапсуляція – це вважати що це можливість зробити поля приватними і додати до кожного з них по get і set методу відповідно. Концептуально це мало чим відрізняється від класу з набором публічних полів. Це тісно перекликається з поняттями об’єкту та структури, що детально розглядаються в розділі Objects and Data Structures книги Clean Code Роберта Мартіна. Розглянемо простий приклад.

package com.in6k.incamp.oop.bad;

public class Car {

   int speed;

   public int getSpeed() {
       return speed;
   }

   public void setSpeed(int speed) {
       this.speed = speed;
   }

}

В цьому прикладі ми ніяк не приховуємо стан об’єктів класу Car, тобто його швидкість. Вона може бути змінена в будь-який момент на будь-яке значення, що не відповідає умовам поводження машин в реальному світі. Ми не можемо одразу змінити швидкість з нуля до 250 км/годину, мати від’ємну швидкість або різко зменшити швидкість до нуля. Розглянемо інший приклад.

package com.in6k.incamp.oop.good;

public class Car {

   private static final int SPEED_STEP = 30;
   private static final int MAX_SPEED = 300;

   private int speed = 0;

   public void accelerate() {
       if (speed >= MAX_SPEED) return;
       this.speed += SPEED_STEP;
   }

   public void brake() {
       if (speed == 0) return;
       this.speed -= SPEED_STEP;
   }

   public int getSpeed() {
       return speed;
   }

}

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

Наслідування

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

Інший невдалий приклад – це наслідування різних брендів або моделей авто від класу автомобіль. Так, BMW X6 це автомобіль, але в цілому виділення для нього окремого класу не має сенсу. Цей клас ніяк не конкретизує відомості, які можуть бути вказані про будь-яке авто. В даному випадку правильним є вказати окреме поле, що буде містити інформацію про модель автомобіля.

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

Наслідування може використовуватись з двох причин:

  • Повторне використання структури класу та поведінки у випадку, коли це наслідування відповідає зв’язку “is a” або “являється”.
  • Як засіб вказати, що сам клас або його похідні реалізовують конкретний інтерфейс взаємодії з ними.

Повторне використання коду

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

Наслідування інтерфейсу

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

Поліморфізм

Щодо поліморфізму в мене є декілька порад:

  • З точки зору програмного коду для виводу статичного тексту на консоль поліморфізм не потрібен. Я не раз бачив ієрархію класів, єдина задача яких виводити статичний текст – не цікаво.
  • Перевантаження методів різними аргументами в контексті ООП не є поліморфізмом. Точніше, це не той поліморфізм, який ми маємо на увазі, коли говоримо про ООП.
  • Демонстрація його використання має проявлятися в клієнтському коді (хоча б в  Main класі) за умови, що клієнт не знає з екземплярами яких класів він має справу.

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

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

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

Підводячи підсумки

Сподіваюсь, в цій стислій статті мені вдалось розкрити вам основні недоліки в розумінні принципів ООП та їх використання, з якими я найчастіше мав справу. Також я раджу переглянути розділ Object Oriented Model, де ви знайдете більше деталей та прикладів використання принципів ООП. Успіхів вам у навчанні, і не забувайте про практику!

Post A Reply