JavaRush /Java блог /Random UA /Принципи ООП
Автор
Milan Vucic
Репетитор по программированию в Codementor.io

Принципи ООП

Стаття з групи Random UA
Java є об'єктно-орієнтованою мовою. Це означає, що писати програми на Java потрібно із застосуванням об'єктно-орієнтованого стилю. І цей стиль заснований на використанні в програмі об'єктів і класів.

Основні принципи ООП:

Принципи ООП - 1Спробуємо за допомогою прикладів розібратися, що таке класи та об'єкти, а також про те, як застосовувати на практиці основні принципи ОВП: абстракцію, успадкування, поліморфізм та інкапсуляцію.

Що таке об'єкт?

Світ, де ми живемо, складається з об'єктів. Якщо ми побачимо довкола, то побачимо, що нас оточують будинки, дерева, автомобілі, меблі, посуд, комп'ютери. Всі ці предмети є об'єктами, і кожен із них має набір певних характеристик, поведінкою та призначенням. Ми звикли до об'єктів, і ми їх використовуємо завжди для конкретних цілей. Наприклад, якщо нам необхідно доїхати до роботи, ми користуємося автомобілем, якщо захочемо поїсти посудом, а якщо відпочити – нам знадобиться зручний диван. Людина звикла мислити об'єктно для вирішення завдань у повсякденному житті. Це стало однією з причин використання об'єктів у програмуванні, а такий підхід до створення програм назвали об'єктно-орієнтованим. Наведемо приклад. Уявіть, що ви розробабо нову модель телефону та хочете налагодити її серійне виробництво. Як розробник телефону, ви знаєте для чого він потрібен, як він функціонуватиме, і з яких деталей він складатиметься (корпус, мікрофон, динамік, дроти, кнопки і т.д.). При цьому лише ви знаєте, як поєднати ці деталі. Однак, ви не плануєте випускати телефони особисто, для цього у вас є цілий штат працівників. Щоб вам не довелося щоразу пояснювати, як з'єднати деталі телефону, і щоб усі телефони при виробництві виходабо однаковими, перш ніж розпочати їх випуск, вам доведеться зробити креслення у вигляді опису пристрою телефону. для цього ви маєте цілий штат працівників. Щоб вам не довелося щоразу пояснювати, як з'єднати деталі телефону, і щоб усі телефони при виробництві виходабо однаковими, перш ніж розпочати їх випуск, вам доведеться зробити креслення у вигляді опису пристрою телефону. для цього ви маєте цілий штат працівників. Щоб вам не довелося щоразу пояснювати, як з'єднати деталі телефону, і щоб усі телефони при виробництві виходабо однаковими, перш ніж розпочати їх випуск, вам доведеться зробити креслення у вигляді опису пристрою телефону.У ООП такий опис, креслення, схема чи шаблон називається класом, з якого під час виконання програми створюється об'єкт. Клас - це опис ще не створеного об'єкта, як загальний шаблон, що складається з полів, методів і конструктора, а об'єкт - екземпляр класу, створений на основі цього опису.

Абстракція ОВП

Давайте тепер подумаємо, як нам перейти від об'єкта з реального світу до об'єкта у програмі на прикладі телефону. Історія цього засобу зв'язку перевищує 100 років і сучасний телефон, на відміну від свого попередника з 19 століття, є набагато складнішим пристроєм. Коли ми користуємося телефоном, то не замислюємося про його пристрій та процеси, що відбуваються всередині нього. Ми просто використовуємо функції, надані розробниками телефону – кнопки або сенсорний екран для вибору номера та здійснення дзвінків. Одним із перших інтерфейсів телефону була рукоятка, яку потрібно було обертати, щоб зробити виклик. Зрозуміло, це було дуже зручно. Проте свою функцію рукоятка справно виконувала. Якщо подивитися на найсучасніший і на перший телефон, можна відразу виділити найважливіші деталі, які важливі і для влаштування кінця 19-го століття, і для суперсучасного смартфона. Це здійснення виклику (набір номера) та прийом виклику. По суті, це те, що робить телефон телефоном, а не чимось іншим. Наразі ми застосували принцип у ОВП – виділення найважливіших характеристик та інформації про об'єкт.Цей принцип ОВП називається абстракцією. Абстракцію в ОВП можна також визначити, як спосіб представлення елементів завдання із реального світу у вигляді об'єктів у програмі. Абстракція завжди пов'язана з узагальненням деякої інформації про властивості предметів або об'єктів, тому головне - це відокремити значущу інформацію від незначної в контексті завдання. У цьому рівнів абстракції може бути кілька. Спробуємо застосувати принцип абстракції до наших телефонів. Для початку виділимо найпоширеніші типи телефонів від перших і до наших днів. Наприклад, їх можна подати у вигляді діаграми, наведеної на малюнку 1. Принципи ООП - 2Тепер за допомогою абстракції ми можемо виділити в цій ієрархії об'єктів загальну інформацію: загальний абстрактний тип об'єктів – телефон, загальну характеристику телефону – рік його створення, та загальний інтерфейс – всі телефони здатні приймати та надсилати дзвінки. Ось як це виглядає на Java:
public abstract class AbstractPhone {
    private int year;

    public AbstractPhone(int year) {
        this.year = year;
    }
    public abstract void call(int outputNumber);
    public abstract void ring (int inputNumber);
}
На підставі цього абстрактного класу ми зможемо створювати нові типи телефонів у програмі з використанням інших базових принципів ООП Java, які розглянемо нижче.

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

За допомогою абстракції ми виокремлюємо спільне для всіх об'єктів. Однак кожна модель телефону індивідуальна і чимось відрізняється від інших. Як же нам у програмі провести кордони та позначити цю індивідуальність? Як зробити так, щоб ніхто з користувачів випадково чи навмисно не зміг зламати наш телефон чи спробувати переробити одну модель в іншу? Для світу реальних об'єктів відповідь очевидна: потрібно помістити всі деталі у корпус телефону. Адже якщо цього не зробити і залишити всі нутрощі телефону та дроту, що з'єднують їх зовні, обов'язково знайдеться допитливий експериментатор, який захоче “поліпшити” роботу нашого телефону. Для виключення подібного втручання у конструкцію та роботу об'єкта в ОВП використовують принцип інкапсуляції.- ще один базовий принцип ООП, при якому атрибути та поведінка об'єкта об'єднуються в одному класі, внутрішня реалізація об'єкта ховається від користувача, а для роботи з об'єктом надається відкритий інтерфейс. Завдання програміста — визначити, які атрибути та методи будуть доступні для відкритого доступу, а які є внутрішньою реалізацією об'єкта та мають бути недоступними для змін.

Інкапсуляція та керування доступом

Допустимо, при виробництві на тильній стороні телефону гравірується інформація про нього: рік його випуску або логотип компанії виробника. Ця інформація цілком конкретно характеризує цю модель його стан. Можна сказати, розробник телефону подбав про незмінність цієї інформації — навряд чи комусь спаде на думку видаляти гравіювання. У світі Java стан майбутніх об'єктів описується у класі з допомогою полів, які поведінка – з допомогою методів. Можливість зміни стану і поведінки здійснюється за допомогою модифікаторів доступу до полів і методів – private, protected, public, а такожdefault(доступ за замовчуванням). Наприклад, ми вирішабо, що рік створення, назва виробника телефону та один із методів відносяться до внутрішньої реалізації класу та не підлягають зміні іншими об'єктами у програмі. За допомогою коду клас можна описати так:
public class SomePhone {

    private int year;
    private String company;
    public SomePhone(int year, String company) {
        this.year = year;
        this.company = company;
    }
private void openConnection(){
    //findComutator
    //openNewConnection...
}
public void call() {
    openConnection();
    System.out.println("Викликаю номер");
}

public void ring() {
    System.out.println("Дзынь-дзынь");
}

 }
Модифікатор privateробить доступними поля та методи класу лише всередині класу. Це означає, що отримати доступ до privateполів ззовні неможливо, як немає можливості викликати privateметоди. Приховування доступу до методу openConnection, залишає нам також можливість вільного зміни внутрішньої реалізації цього методу, оскільки цей метод гарантовано не використовується іншими об'єктами і не порушить їх роботу. Для роботи з нашим об'єктом ми залишаємо відкритими методи callта ringза допомогою модифікатора public. Надання відкритих методів для роботи з об'єктом також є частиною механізму інкапсуляції, оскільки, якщо повністю закрити доступ до об'єкта, він стане марним.

успадкування

Погляньмо ще раз на діаграму телефонів. Можна помітити, що вона є ієрархією, в якій модель, розташована нижче має всі ознаки моделей, розташованих вище по гілці, плюс своїми власними. Наприклад, смартфон, що використовує стільникову мережу для зв'язку (має властивості стільникового телефону), є бездротовим і переносним (має властивості бездротового телефону) і може приймати і робити виклики (властивості телефону). І тут ми можемо говорити про успадкування властивостей об'єкта. У програмуванні наслідування полягає у використанні вже існуючих класів для опису нових. Розглянемо приклад створення класу смартфон за допомогою наслідування. Всі бездротові телефони працюють від акумуляторів, які мають певний ресурс роботи в годинах. Тому додамо цю властивість до класу бездротових телефонів:
public abstract class WirelessPhone extends AbstractPhone {

    private int hour;

    public WirelessPhone(int year, int hour) {
        super(year);
        this.hour = hour;
    }
    }
Стільникові телефони успадковують властивості бездротового телефону, ми також додали в цей клас реалізацію методів callі ring:
public class CellPhone extends WirelessPhone {
    public CellPhone(int year, int hour) {
        super(year, hour);
    }

    @Override
    public void call(int outputNumber) {
        System.out.println("Викликаю номер" + outputNumber);
    }

    @Override
    public void ring(int inputNumber) {
        System.out.println("Вам дзвонить абонент" + inputNumber);
    }
}
І, нарешті, клас смартфон, який, на відміну від класичних стільникових телефонів, має повноцінну операційну систему. У смартфон можна додавати нові програми, що підтримуються цією операційною системою, розширюючи таким чином його функціональність. За допомогою коду клас можна описати так:
public class Smartphone extends CellPhone {

    private String operationSystem;

    public Smartphone(int year, int hour, String operationSystem) {
        super(year, hour);
        this.operationSystem = operationSystem;
    }
public void install(String program){
    System.out.println("Встановлюю" + program + "для" + operationSystem);
}

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

Поліморфізм

Якщо ми подивимося на всі моделі телефонів, то, незважаючи на відмінності у зовнішньому вигляді та пристрої моделей, ми можемо виділити у них певну загальну поведінку – всі вони можуть приймати та здійснювати дзвінки та мають досить зрозумілий та простий набір кнопок управління. Застосовуючи відомий нам вже один з основних принципів ООП абстракцію в термінах програмування можна сказати, що телефон має один спільний інтерфейс. Тому користувачі телефонів можуть цілком комфортно користуватися різними моделями, використовуючи ті самі кнопки керування (механічні або сенсорні), не вдаючись у технічні тонкощі пристрою. Так, ви постійно користуєтеся стільниковим телефоном, і легко зможете здійснити дзвінок з його стаціонарного побратима. Принцип ООП,поліморфізмом . Давайте уявимо, що нам у програмі потрібно описати користувача, який може користуватися будь-якими моделями телефону, щоб зателефонувати до іншого користувача. Ось як це можна зробити:
public class User {
    private String name;

    public User(String name) {
        this.name = name;
            }

    public void callAnotherUser(int number, AbstractPhone phone){
// Ось він поліморфізм - використання в коді абстактного типу AbstractPhone phone!
        phone.call(number);
    }
}
 }
Тепер опишемо різні моделі телефонів. Одна з перших моделей телефонів:
public class ThomasEdisonPhone extends AbstractPhone {

public ThomasEdisonPhone(int year) {
    super(year);
}
    @Override
    public void call(int outputNumber) {
        System.out.println("Повертайте ручку");
        System.out.println("Повідомте номер абонента, сер");
    }

    @Override
    public void ring(int inputNumber) {
        System.out.println("Телефон дзвонить");
    }
}
Звичайний стаціонарний телефон:
public class Phone extends AbstractPhone {

    public Phone(int year) {
        super(year);
    }

    @Override
    public void call(int outputNumber) {
        System.out.println("Викликаю номер" + outputNumber);
    }

    @Override
    public void ring(int inputNumber) {
        System.out.println("Телефон дзвонить");
    }
}
І, нарешті, крутий відеотелефон:
public class VideoPhone extends AbstractPhone {

    public VideoPhone(int year) {
        super(year);
    }
    @Override
    public void call(int outputNumber) {
        System.out.println("Підключаю відеоканал для абонента" + outputNumber );
    }
    @Override
    public void ring(int inputNumber) {
        System.out.println("У вас вхідний відеодзвінок..." + inputNumber);
    }
  }
Створимо об'єкти в методі main()та протестуємо метод callAnotherUser:
AbstractPhone firstPhone = new ThomasEdisonPhone(1879);
AbstractPhone phone = new Phone(1984);
AbstractPhone videoPhone=new VideoPhone(2018);
User user = new User("Андрій");
user.callAnotherUser(224466,firstPhone);
// Повертайте ручку
//Повідомте номер абонента, сер
user.callAnotherUser(224466,phone);
// Викликаю номер 224466
user.callAnotherUser(224466,videoPhone);
//Підключаю відеоканал для абонента 224466
Використовуючи виклик того самого методу об'єкта user, ми отримали різні результати. Вибір конкретної реалізації методу callвсередині методу callAnotherUserпроводився динамічно на підставі конкретного типу об'єкта, що викликає його, в процесі виконання програми. У цьому полягає основна перевага поліморфізму – вибір реалізації у процесі виконання програми. У прикладах класів телефонів, наведених вище, ми використовували перевизначення методів – прийом, у якому змінюється реалізація методу, визначена базовому класі, без зміни сигнатури методу. По суті, це є заміною методу, і саме новий метод, визначений у підкласі, викликається при виконанні програми. Зазвичай при перевизначенні методу використовується анотація.@Override, яка підказує компілятору про необхідність перевірити сигнатури перевизначуваного та перевизначального методів. У підсумку , щоб стиль вашої програми відповідав концепції ООП та принципам ООП java дотримуйтесь наступних порад:
  • виділяйте основні параметри об'єкта;
  • виділяйте загальні властивості та поведінку та використовуйте успадкування при створенні об'єктів;
  • використовуйте абстрактні типи для опису об'єктів;
  • намагайтеся завжди приховувати методи та поля, що належать до внутрішньої реалізації класу.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ