Принцип поліморфізму в об'єктно-орієнтованому програмуванні. Концепція об'єктно-орієнтованого програмування JAVA. Призначення механізму перевизначення методів

Вітаю! Це стаття про один із принципів ООП - поліморфізм.

Що таке поліморфізм

Визначення поліморфізму звучить жахливо 🙂

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

Слово " поліморфізмМоже здатися складним - але це не так. Потрібно просто розбити дане визначенняна частини та показати на прикладах, що мають на увазі. Повірте, вже наприкінці статті це визначення поліморфізму здасться Вам зрозумілим 🙂

Поліморфізм, якщо перекласти, - це означає "багато форм".Наприклад, актор у театрі може приміряти він багато ролей - чи приймати "багато форм".

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

То про які форми йдеться?Давайте спочатку наведемо приклади та покажемо, як на практиці проявляється поліморфізм, а потім знову повернемося до його визначення.

Як виявляється поліморфізм

Справа в тому що якби в Java не було принципу поліморфізму, компілятор би інтерпретував це як помилку:

Як бачите, методи на картинці відрізняються значеннями, які вони набувають:

  • перший приймає int
  • а другий приймає String

Однак, оскільки в Java використовується принцип поліморфізму, компілятор не сприйматиме це як помилку, тому що такі методи вважатимуться різними:

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

Як бачите, ми не повинні вигадувати окрему назву для кожного методу – а головне їх запам'ятовувати! Дуже зручно.

Тепер Ви можете зрозуміти чому часто цей принцип описують фразою:

Один інтерфейс – багато методів

Це передбачає, що ми можемо заповнити одну назву (один інтерфейс), яким ми зможемо звертатися до кількох методів.

Перевантаження методів

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

Перевизначення методів батька

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

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

Приклад

Уявімо, що у нас є така структура:

Вгорі ієрархії класів стоїть клас Animal. Його успадковують три класи - Cat, Dogі Cow.

Клас "Animal" має метод "голос" (voice). Цей метод відображає повідомлення "Голос". Звичайно, ні собака, ні кішка не кажуть "Голос" 🙂 Вони гавкають і нявкають. Відповідно, Вам потрібно задати інший метод для класів Cat, Dogі Cow- щоб кішка нявкала, собака гавкала, а корова говорила "Муу".

Тому, у класах-спадкоємцях ми перевизначаємо метод voice(), щоб ми в консолі отримували "Мяу", "Гав" та "Муу".

  • Зверніть увагу: перед методом, який ми перевизначаємо, пишемо " @OverrideЦе дає зрозуміти компілятору, що ми хочемо перевизначити метод.

Так що ж таке поліморфізм

Проте поліморфізм - це принцип. Всі реальні приклади, які ми навели вище - це лише способиреалізації поліморфізму.

Давайте знову подивимося на визначення, яке ми давали на початку статті:

Поліморфізм- можливість застосування однойменних методів з однаковими чи різними наборами параметрів у одному класі чи групі класів, пов'язаних ставленням успадкування.

Виглядає зрозуміліше, правда? Ми показали, як можна:

  • створювати "одноіменні методи" в одному класі ("перевантаження методів")
  • або змінити поведінку методів батьківського класу ("перевизначення методів").

Усе це - прояви " підвищеної гнучкості " об'єктно-орієнтованих мов завдяки поліморфізму.

Сподіваємось, наша стаття була Вам корисною. Записатися на наші курси з Java можнау нас на .

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

Клас (java class) визначає пристрій та поведінку об'єктів. Пристрій описується через набір характеристик (властивостей), а поведінка – через набір доступних об'єктів операцій (методів). Класи можна створювати на основі вже наявних, додаючи або перевизначаючи властивості та методи.

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

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

Спадкування є невід'ємною частиною Java. При використанні успадкування враховується, що новий клас, що успадковує властивості базового (батьківського) класу, має всі ті властивості, які має батько. У коді використовується операнд extends, після якого зазначається ім'я базового класу. Тим самим відкривається доступ до всіх полів та методів базового класу.

Використовуючи наслідування, можна створити загальний "java class", який визначає характеристики, загальні для набору зв'язаних елементів. Потім можна успадковуватися від нього та створити додаткові класи, для яких визначити додаткові унікальні для них характеристики.

Головний успадкований клас Java називають суперклассом super. Наслідуючий клас називають підкласом. Таким чином, підклас - це спеціалізована версія суперкласу, яка успадковує всі властивості суперкласу і додає свої власні унікальні елементи.

Розглянемо приклад опису java class"a студента Student, який має ім'я, прізвище, вік, та номер групи. Клас студента будемо створювати на основі super класу користувача User, у якого вже визначено ім'я, прізвище та вік:

Public class User ( int age; String firstName; String lastName; // Конструктор public User(int age, String firstName, String lastName) ( this.age = age; this.firstName = firstName; this.lastName = lastName; ) )

Тепер створюємо окремий клас Student, що успадковує властивості super класу. При успадкування класу необхідно також перевизначити конструктори батьківського класу:

Public class Student extends User ( int group; // Конструктор public Student(int age, String firstName, String lastName) ( super(age, firstName, lastName); ) boolean isMyGroup(int g) ( return g == group; ) )

Ключове слово extendsпоказує, що успадковуємося від класу User.

Ключове слово super

У конструкторі класу Student ми викликаємо конструктор батьківського класу через оператор super, передаючи йому весь потрібний набір параметрів. У Java ключове слово superозначає суперклас, тобто. клас, похідним якого є поточний клас. Ключове слово super можна використовувати для виклику конструктора суперкласу та звернення до члена суперкласу, прихованого членом підкласу.

Розглянемо як відбувається успадкуванняз точки зору створення об'єкта:

Student student = new Student(18, "Киса", "Вороб'янінов", 221);

Спочатку відкривається конструктор класу Student, після цього викликається конструктор суперкласу User, а потім виконуються операції, що залишилися в конструкторі Student. Така послідовність дій цілком логічна і дозволяє створювати складніші об'єкти на основі простіших.

У суперкласу може бути кілька перевантажених версій конструкторів, тому можна викликати метод super() з різними параметрами. Програма виконає конструктор, який відповідає зазначеним аргументам.

Друга форма ключового слова superдіє подібно до ключового слова thisТільки при цьому ми завжди посилаємося на суперклас підкласу, в якому вона використана. Загальна форма має такий вигляд:

Тут член може бути шляхом або змінної екземпляра. Подібна форма підходить у тих випадках, коли імена членів підкласу приховують члени суперкласу з такими самими іменами.

Class A ( int i; ) // успадковуємося від класу A class B extends A ( int i; // ім'я змінної збігається і приховує змінну i у класі A B(int a, int b) ( super.i = a; // звертаємось до змінної i з класу A i = b; // звертаємося до змінної i з класу B) void show() ( System.out.println("i з суперкласу дорівнює "+ super.i); System.out.println(" i в підкласі дорівнює " + i); ) ) class MainActivity ( B subClass = new B(1, 2); subClass.show(); )

В результаті в консолі ми маємо побачити:

I із суперкласу дорівнює 1 i у підкласі дорівнює 2

Перевизначення методів, Override

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

Якщо потрібно отримати доступ до версії перевизначеного методу, визначеного в суперкласі, необхідно використовувати ключове слово super.

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

У Java SE5 з'явилася анотація @Override;. Якщо необхідно перевизначити метод, використовуйте @Override, і компілятор видасть повідомлення про помилку, якщо замість перевизначення буде випадково виконане перевантаження.

У Java можна успадковуватись тільки від одного класу.

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

В інформатиці інкапсуляцією (лат. en capsula) називається упаковка даних та/або функцій у єдиний об'єкт.

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

Модифікатори доступу

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

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

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

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

Приклад простого опису робота

Public class Robot (private double x = 0; // Поточна координата X private double y = 0; // Поточна координата Y private double course = 0; // Поточний курс (у градусах) public double getX() ( return x; ) public void setX(double x) ( this.x = x; ) public double getY() ( return y; ) public void setY(double y) ( this.y = y; ) public double getCourse() ( return course; ) // Визначення курсу public void setCourse(double course) (this.course = course;) // Пересування на дистанцію public void forward(int distance) ( // Звернення до поля об'єкта X x = x + distance * Math.cos( course / 180 * Math.PI);// Звернення до поля об'єкта Y y = y + distance * Math.sin(course / 180 * Math.PI); ) // Друк координат робота .println(x + "," + y); ) )

У представленому прикладі робота використовуються набори методів, що починають setі get. Цю пару методів часто називають сеттер/гетер. Ці методи використовуються для доступу до полів об'єкта. Найменування способу закінчуються найменуванням поля, що починається з ПРОПИСНОЇ літери.

У методах setми передаємо значення через формальний параметр усередину процедури. У коді процедури ми надаємо значення змінної об'єкта/класу з використанням ключового слова this.

This.course = course ...

Використання ключового слова thisнеобхідно, т.к. Найменування формального параметра збігається з найменуванням змінної об'єкта. Якби найменування відрізнялися б, то можна було б thisне використовувати.

Поліморфізм, polymorphism

Поліморфізм є одним із фундаментальних понять в об'єктно- орієнтоване програмуванняпоряд з успадкуванням та інкапсуляцією. Слово поліморфізм грецького походження і означає "що має багато форм". Щоб зрозуміти, що означає поліморфізм стосовно об'єктно-орієнтованого програмування, розглянемо приклад створення векторного графічного редактора, в якому необхідно використовувати низку класів у вигляді набору графічних примітивів - Square, Line, Circle, Triangle, і т.д. У кожного з цих класів необхідно визначити метод drawдля відображення відповідного примітиву на екрані.

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

Людина, незнайома з поліморфізмом, найімовірніше створить кілька масивів: окремий масив кожного типу примітивів і напише код, який послідовно перебере елементи кожного масиву і викличе в кожного елемента метод draw. В результаті вийде приблизно наступний код:

// Визначення масивів графічних примітивів Square s = new Square; Line l = new Line; Circle c = new Circle; Triangle t = New Triangle; // Наповнення всіх масивів відповідними об'єктами. . . // Цикл із перебором всіх осередків масиву. for (int i = 0; i< s.length; i++){ // вызов метода draw() в случае, если ячейка не пустая. if (s[i] != null) s.draw(); } for(int i = 0; i < l.length; i++){ if (l[i] != null) l.draw(); } for(int i = 0; i < c.length; i++){ if (c[i] != null) c.draw(); } for(int i = 0; i < t.length; i++){ if (t[i] != null) t.draw(); }

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

Використовуючи поліморфізм, можна спростити реалізацію подібної функціональності. Насамперед створимо спільний батьківський клас Shape для всіх наших класів.

Public class Shape ( public void draw() ( System.out.println("Заглушка"); ) )

Після цього ми створюємо різні класи-спадкоємці: Square (Квадрат), Line (Лінія), Сircle (коло) та Triangle (Трикутник):

Public class Point extends Shape ( public void draw() ( System.out.println("Квадрат"); ) ) Public class Line extends Shape ( public void draw() public class Shar ( public void draw() ( System.out.println("Коло"); ) ) Public class Triangle extends Shape ( public void draw() ( System.out.println("Трикутник"); ) )

У спадкоємцях ми перевизначено метод draw. В результаті отримали ієрархію класів, зображену на малюнку.

Тепер перевіримо дивовижну можливість поліморфізму:

// Визначення та ініціалізація масиву Shape a = new Shape (new Shape(), new Triangle(), new Square(), new Сircle()); // Перебір у циклі елементів масиву for(int i = 0; i< a.length; i++) { a[i].draw(); }

У консоль буде виведено наступні рядки:

Заглушка Трикутник Квадрат Коло

Таким чином, кожен клас-спадкоємець викликав саме свій метод draw, замість того, щоб викликати метод draw з батьківського класу Shape.

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

Перевантаження методу overload

У процедурному програмуванні теж існує поняття поліморфізму, яке відрізняється від розглянутого механізму ООП. Процедурний поліморфізм передбачає можливість створення декількох процедур або функцій з однаковим ім'ям, але різною кількістю або типами параметрів, що передаються. Такі однойменні функції називаються перевантаженими, саме явище - перевантаженням (overload). Перевантаження функцій існує у ООП і називається перевантаженням методів. Прикладом використання навантаження методів у мові Java може бути клас PrintWriter , який використовується зокрема виведення повідомлень на консоль. Цей клас має безліч методів println, які відрізняються типами та/або кількістю вхідних параметрів. Ось лише кілька з них:

Void println() // перехід на новий рядок void println(boolean x) // виводить значення булевської змінної (true або false) void println(String x) // виводить рядок - значення текстового параметра

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

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

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

Відповідно до UML( Unified Modelling Language - уніфікований мова моделювання), клас має таке графічне уявлення.

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

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

  • конструктор(constructor) - виконується під час створення об'єктів;
  • деструктор(destructor) - виконується при знищенні об'єктів.

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

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

Інкапсуляція(encapsulation) - це приховування реалізації класу та відокремлення його внутрішнього уявлення від зовнішнього (інтерфейсу). При використанні об'єктно-орієнтованого підходу не прийнято застосовувати прямий доступ до властивостей будь-якого класу методів інших класів. Для доступу до властивостей класу прийнято використовувати спеціальні методи цього класу для отримання та зміни його властивостей.

Усередині об'єкта дані та методи можуть мати різний ступінь відкритості (або доступності). Ступені доступності, прийняті в мові Java, докладно будуть розглянуті в лекції 6. Вони дозволяють більш тонко керувати властивістю інкапсуляції.

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

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

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

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

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

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

успадкування(inheritance) - це відношення між класами, при якому клас використовує структуру або поведінку іншого класу (одинкове успадкування), або інших (множинне успадкування) класів. Спадкування вводить ієрархію "загальне/приватне", в якій підкласуспадковує від одного або кількох більш загальних суперкласів. Підкласи зазвичай доповнюють або перевизначають успадковану структуру та поведінку.

Як приклад можна розглянути завдання, в якому необхідно реалізувати класи "Легковий автомобіль" та "Вантажний автомобіль". Очевидно, ці два класи мають загальну функціональність. Так, обидва вони мають чотири колеса, двигун, можуть переміщатися і т.д. Всі ці властивості має будь-який автомобіль, незалежно від того, вантажний він або легковий, 5- або 12-місний. Розумно винести ці загальні властивостіі функціональність в окремий клас, наприклад, "Автомобіль" і успадковувати від нього класи "Легковий автомобіль" та "Вантажний автомобіль", щоб уникнути повторного написання одного і того ж коду в різних класах.


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

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

У розглянутому прикладі застосовано поодиноке успадкування. Деякий клас також може успадковувати властивості та поведінку відразу кількох класів. Найбільш популярним прикладом застосування множинного успадкування є проектування системи обліку товарів у зоомагазині.

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

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

Не всі об'єктно-орієнтовані мови програмування містять мовні конструкції для опису множинного успадкування.

У мові Java множинне успадкування має обмежену підтримку через інтерфейси і буде розглянуто у лекції 8.

Поліморфізм

Поліморфізмє одним з фундаментальних понять у об'єктно-орієнтоване програмуванняпоряд з успадкуванням та інкапсуляцією. Слово "поліморфізм" грецького походження і означає "що має багато форм". Щоб зрозуміти, що воно означає стосовно об'єктно-орієнтоване програмуванняРозглянемо приклад.

Припустимо, хочемо створити векторний графічний редактор, у якому нам потрібно описати як класів набір графічних примітивів - Point , Line , Circle , Box тощо. У кожного з цих класів визначимо метод draw для відображення відповідного примітиву на екрані.

Очевидно, доведеться написати код, який при необхідності відобразити малюнок, буде послідовно перебирати всі примітиви, що на момент відображення знаходяться на екрані, і викликати метод draw у кожного з них. Людина, не знайома з поліморфізмом, найімовірніше, створить кілька масивів (окремий масив для кожного типу примітивів) і напише код, який послідовно перебере елементи з кожного масиву і викличе у кожного елемента метод draw. В результаті вийде приблизно наступний код:

... // створення порожнього масиву, який може // містити об'єкти Point з максимальним // обсягом 1000 Point p = new Point; Line l = new Line; Circle c = new Circle; Box b = New Box; ... // припустимо, тут відбувається // заповнення всіх масивів відповідними // об'єктами... for(int i = 0; i< p.length;i++) { //цикл с перебором всех ячеек массива. //вызов метода draw() в случае, // если ячейка не пустая. if(p[i]!=null) p[i].draw(); } for(int i = 0; i < l.length;i++) { if(l[i]!=null) l[i].draw(); } for(int i = 0; i < c.length;i++) { if(c[i]!=null) c[i].draw(); } for(int i = 0; i < b.length;i++) { if(b[i]!=null) b[i].draw(); } ...

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

Використовуючи поліморфізм, ми можемо спростити реалізацію подібної функціональності. Насамперед, створимо спільну батьківську

JAVA ґрунтується на концепціях об'єктно-орієнтованого програмування, що дозволяє перейти на більш високий рівень абстракції, щоб вирішити будь-яку проблему реалістичним шляхом. Об'єктно-орієнтований підхід концептуалізує вирішення проблеми у площині об'єктів реального світу, які легше повторно використовувати у додатку. Наприклад, Chair (стул), Fan (вентилятор), Dog (Собака), Computer (комп'ютер) тощо. У JAVA клас є макет, шаблон або прототип, який визначає загальну поведінку об'єкта даного типу. Примірник - це окрема реалізація класу, і всі екземпляри класу мають однакові властивості, описані у визначенні класу. Наприклад, ви можете визначити клас з ім'ям House (будинок) з кількістю кімнат як атрибут і створити екземпляри класу, такі як будинок з двома кімнатами, будинок з трьома кімнатами і так далі. Переваги:Нижче наведено деякі плюси об'єктно-орієнтованої розробки програмного забезпечення(ПЗ).

  • Зниження витрат за підтримку ПЗ, переважно з допомогою те, що вона здійснюється модульно.
  • Удосконалене повторне використання коду завдяки таким якостям, як успадкування, і, як наслідок, швидша розробка ПЗ.
  • Підвищена надійність та гнучкість коду.
  • Легкість розуміння внаслідок моделювання реального світу.
  • Найкраща абстракція на рівні об'єкта.
  • Зменшення складності переходу від однієї фази розробки до іншої.
Є чотири основні характеристики ООП:
  • Інкапсуляція
  • успадкування
  • Поліморфізм
  • Абстракція

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

Інкапсуляція виступає договором для об'єкта, що він повинен приховати, що відкрити для доступу іншими об'єктами. У JAVA ми використовуємо модифікатор private доступу для того, щоб приховати метод і обмежити доступ до змінної з зовнішнього світу. JAVA також має у своєму розпорядженні різні модифікатори доступу: public, за замовчуванням, protected, private, які використовуються для обмеження видимості на різних рівнях. Але кінцевою метою є інкапсуляція тих речей, які повинні бути змінені. Найкраще працює підхід, у якому, у класу має бути лише одна причина зміни, і інкапсулювання втілює у реальність проектування цієї “однієї причини”. Правильним в інкапсуляції вважається приховування речей, що часто змінюються, щоб уникнути пошкодження інших класів. Переваги:Нижче представлені деякі переваги інкапсуляції:
  • Ми можемо захистити внутрішній стан об'єкта за допомогою приховування його атрибутів.
  • Це покращує модульну побудову коду, оскільки запобігає взаємодії об'єктів несподіваними способами.
  • Підвищується практичність коду.
  • Це підтримує договірні відносини конкретного об'єкта.
  • Інкапсуляція полегшує підтримку ПЗ.
  • Зміни в коді можуть здійснюватися незалежно.

Поліморфізм

Поліморфізм у програмуванні - це здатність надавати той самий інтерфейс для різних базових форм (типів даних). Це означає, що класи, що мають різну функціональність, спільно використовують той самий інтерфейс і можуть бути динамічно викликані передачею параметрів за посиланням. Класичний приклад - це клас Shape (фігура) і всі класи, що успадковуються від нього: square (квадрат), circle (круг), dodecahedron (додекаедр), irregular polygon (неправильний багатокутник), splat (клякса) тощо. У цьому прикладі кожен клас матиме свій метод Draw() і клієнтський код може просто робити: Shape shape = new Shape () ; Shape.area() щоб отримати коректну поведінку будь-якої фігури Краса поліморфізму полягає в тому, що код, працюючи з різними класами, не повинен знати, який клас він використовує, тому що всі вони працюють за одним принципом. Процес, застосовуваний объектно-ориентированными мовами програмування реалізації динамічного поліморфізму, називається динамічним зв'язуванням. Примітка:Поліморфізм - це здатність вибирати конкретніші методи виконання залежно від об'єкта. Поліморфізм здійснюється тоді, коли задіяні абстракні класи. Переваги:
  • Створення повторного коду. Тобто, щойно клас створено, реалізований і протестований, може вільно використовуватися без турботи у тому, що у ньому написано.
  • Це забезпечує більш універсальний і слабопов'язаний код.
  • Знижується час компіляції, що прискорює розробку.
  • Динамічний зв'язування.
  • Один і той же інтерфейс може бути використаний для створення методів із різними реалізаціями.
  • Вся реалізація може бути замінена за допомогою використання однакових сигнатур методу.
Перевизначення методів як поліморфізму.Перевизначення взаємодіє з двома методами: методом батьківського класу та методом похідного класу. Ці методи мають поодинокі ім'я та сигнатури. Перевизначення дозволяє вам робити одну і ту ж операцію різними шляхами для різних типівоб'єктів. Наприклад: while (it. hasNext () ) ( Shape s = ( Shape) it. next () ; totalArea += s. area (dim) ; //буде застосовано поліморфізм і викликаний потрібний метод для кожного об'єкта. } Перезавантаження методів або ad-hoc поліморфізм або статичний поліморфізмПерезавантаження взаємодіє із кількома методами одного класу, які однаково названі, але мають різні сигнатури методів. Перезавантаження дозволяє вам описати ту саму операцію різними шляхами для різних даних. Іноді її називають статичним поліморфізмом, але фактично поліморфізм вона не є. Це ніщо інше як просто наявність двох методів з однаковими іменами, але різним списком аргументів. Перезавантаження немає нічого спільного з успадкуванням і поліморфізмом. І перезавантажений метод зовсім не те саме, що перевизначений метод. Параметричний поліморфізм через узагальнення у JAVAПри оголошенні класу поле імені може асоціюватися з різними типами, А ім'я методу може асоціюватися з різними параметрами та типами, що повертаються. JAVA підтримує параметричний поліморфізм, використовуючи узагальнення (дженерики). List < String> list=new ArrayList < String> () ; Чому ми не можемо перевизначити статичний метод у JAVA?Перевизначення залежить від наявності екземпляра класу. Ідея поліморфізму полягає в тому, що ви можете створити підклас, і об'єкти, що реалізуються тими підкласами, будуть поводитися по-іншому з тими самими методами батьківського класу (перевизначеними в підкласах). Статичний метод не асоціюється до жодних екземплярів класу, таким чином, сама концепція перевизначення не може бути застосована. Творцями JAVA керували дві міркування, які вплинули такий підхід. По-перше, це проблеми виконання коду: лилося дуже багато критики на адресу Smalltalk через повільну роботу (складальник сміття та поліморфізм були частиною цієї проблеми), і в проектуванні JAVA намагалися цього уникнути. Другим міркуванням було рішення, що цільовою аудиторією JAVA стануть С++ розробники. Те, що статичні методи працюють саме таким чином, було дуже знайоме C++ програмістам, а також прискорювало роботу, тому що не було необхідності проходити вгору по ієрархії класів, щоб з'ясувати, який метод викликати. Ви йдете прямо до класу та викликаєте конкретний метод.

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

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

Абстракція

Абстракція означає розробку класів виходячи з їх інтерфейсів та функціональності, не беручи до уваги реалізацію деталей. Абстрактний клас є інтерфейсами без включення фактичної реалізації. Він відрізняє реалізацію об'єкта з його поведінки. Абстракція полегшує код, приховуючи несуттєві деталі. Переваги:
  • Застосовуючи абстракцію, ми можемо відокремити те, що може бути згруповано з якогось типу.
  • Часто змінювані властивості та методи можуть бути згруповані в окремий тип, таким чином основний тип не буде змінюватися. Це посилює принцип ООП: "Код повинен бути відкритим для Розширення, але закритим для Змін".
  • Абстракція полегшує представлення доменних моделей.
Відмінність між абстракцією та інкапсуляцієюІнкапсуляція – це стратегія, яка використовується як частина абстракції. Інкапсуляція відноситься до структури об'єкта: об'єкти інкапсулюють свої властивості та приховують їх від доступу ззовні. Користувачі класу взаємодіють із ним з допомогою його методів, але мають доступу безпосередньо до структури класу. Отже клас абстрагує деталі реалізації, які стосуються його будову. Абстракція є загальнішим терміном. Вона також може досягатися серед іншого за допомогою підкласів. Наприклад, клас List (список) у стандартній бібліотеці є абстракцією для послідовності елементів, проіндексованих відповідно до їх місця у списку. Конкретними прикладами списку List є ArrayList або LinkedList. Код, який взаємодіє зі списком List, абстрагується від деталей, який саме список він використовує. Часто абстракція неможлива без приховування основного стану за допомогою інкапсуляції. Якщо клас розкриває свою внутрішню структуру, він може змінити свої внутрішні операції, отже, неспроможна абстрагуватися. Що таке абстрактний клас та абстрактний метод?Трапляється, що під час розробки ви хочете, щоб базовий клас представляв лише інтерфейс його похідних класів. Тобто ви не хочете, щоб будь-хто створював екземпляри базового класу. Вам необхідно використовувати інтерфейс таким чином, щоб тільки приводити до нього об'єкти (це неявне приведення, яке забезпечує поліморфну ​​поведінку). Це досягається шляхом створення даного класу абстрактним за допомогою ключового слова abstract. Це накладає деякі обмеження, такі як неможливість створювати екземпляри абстрактного класу, при використанні абстрактного класу необхідно реалізовувати абстрактні методи. Цим забезпечується поліморфізм. Абстрактний клас може містити і абстрактні та конкретні методи. Якщо хоч один метод у класі оголошений абстрактним, весь клас повинен бути оголошений абстрактним. Проте, у зворотний бік правило нічого не винні дотримуватися. Якщо клас оголошений абстрактним, може і містити абстрактні методи. Метод, який лише визначає свої сигнатури і не забезпечує реалізацію, називається абстрактним. Фактична його реалізація залишена підкласам, які розширюють абстрактний клас. Абстрактний метод може бути використаний об'єктом, лише інший клас може його розширити. Коли потрібно використовувати абстрактний клас?Абстрактні класи дозволяють визначити певну поведінку за замовчуванням і змусити підкласи забезпечити будь-яку конкретну поведінку. Наприклад: List (список) є інтерфейсом, у свою чергу AbstractList визначає основну поведінку Списку, яка може бути використана як є або уточнена в підкласі, наприклад, ArrayList (обліковий масив). Що таке інтерфейс?У концепції інтерфейсу лежить абстрактний клас, але інтерфейс (визначається ключовим словом interface) ступив далі. Він запобігає взагалі будь-якій реалізації методу або функції. Ви можете лише оголошувати метод або функцію, але не забезпечувати їхню реалізацію. Клас, який реалізує цей інтерфейс, повинен якраз і подбати про фактичну реалізацію. Інтерфейси дуже корисні та повсюдно використовуються в ООП. Оскільки вони поділяють сам інтерфейс та реалізацію, вони надають багато переваг свого використання:
  1. Множинне успадкування.
  2. Слабка пов'язаність. Відбувається абстракція операції, така як поділ на рівні, а конкретною реалізацією може бути будь-що: JDBC, JPA, JTA і т.д.
  3. Програма-інтерфейс не реалізується.
  4. Поліморфізм із динамічним зв'язуванням: розкривається програмний інтерфейс об'єкта без розкриття його фактичної реалізації
  5. Абстрактні рівні, розподіл функціональностей.
Різниця між інтерфейсом та абстрактним класом.
  • Інтерфейс - це договірні відносини з класами, які цей інтерфейс реалізують, у тому, що реалізація відбувається шляхом, позначеним інтерфейсом. Це порожня оболонка із оголошеними методами.
  • Абстрактний клас визначає деяку загальну поведінку та просить свої підкласи визначити нетипову чи конкретну поведінку для свого класу.
  • Способи і члени абстрактного класу може бути позначені будь-яким модифікатором доступу, своєю чергою всі способи інтерфейсу мають бути відкритими (public).
  • Коли відбувається успадкування абстрактного класу, клас-спадкоємець повинен визначити абстрактні методи, тоді як інтерфейс може успадковувати інший інтерфейс і навіть не обов'язково визначати його методи.
  • Клас-спадкоємець може розширювати лише один абстрактний клас, а інтерфейс може розширювати або клас може реалізовувати багато інших інтерфейсів.
  • Клас-спадкоємець може визначати абстрактні методи з тим же менш обмеженим модифікатором доступу, при цьому клас, що реалізує інтерфейс, повинен визначати методи з тим же рівнем видимості.
  • Інтерфейс не містить конструктори, у той час як вони є в абстрактному класі.
  • Змінні, оголошені в інтерфейсі Java за замовчуванням є final. Анотація клас може містити змінні, які не є final.
  • Усі учасники Java-інтерфейсу за промовчанням є public . Учасники абстрактного класу можуть дозволити собі бути public, protected та ін.

Композиція

Повторне використання коду може бути досягнуто за допомогою успадкування, так і композиції. Але при цьому залучення композиції забезпечує більш високий рівень інкапсуляції, ніж успадкування, тому що зміни в класі back-end не обов'язково торкнуться код, який відноситься до front-end класу. Композиція - це техніка проектування, що у класах відносини типу “has-a” (має, включає у собі). Для повторного використання коду можуть застосовуватися як успадкування java, так і композиція об'єкта. Суть композиції полягає у вираженні відношення "has a" між об'єктами. Подумайте про стілець. У випорожнення є (has a) сидіння. У випорожнення є (has a) спинка. У випорожнення є (has a) певна кількість ніжок. Фраза ”has a” / “є” передбачає відносини, у яких стілець має чи, як мінімум, використовує інший об'єкт. Це і є відносини “has-a”, є основою композиції. Переваги:
  • Контроль видимості
  • Реалізація може бути замінена під час виконання (run-time)
  • Слабка пов'язаність, оскільки клас-інтерфейс залежить від реалізації.
Відмінності між композицією та успадкуванням
Композиція (has a / має)Наслідування (is a / є)
1 Підтримує поліморфізм та повторне використання коду.
2 Об'єкт під час виконання (run-time) вже створено.Об'єкт створюється динамічно під час компіляції.
3 Реалізація може бути замінена під час виконання (run-time).Реалізація може бути замінена під час компіляції.
4 Підклас залежить від класу-батька, що сприяє слабкому зв'язування (особливо під керівництвом інтерфейсу).Підклас є завізистом від реалізації класу-батька, тому зв'язування вважається сильним.
5 Використання: у Будинку є Ванна кімната. Неправильно говорити, що Будинок – це Ванна кімната.Спадкування є односпрямованим: Будинок – це Будівля. Але будівля не є домівкою.
Примітка:Не використовуйте успадкування лише для того, щоб забезпечити повторне використання коду. Якщо немає відношень “is a“ (є), цих цілей використовується композиція. Різниця між композицією та агрегацією у відносинах об'єктів. Агрегація- це взаємозв'язок, коли один клас вписується в колекцію. Це частина цілого відношення, де частина може бути без цілого. Такі відносини набагато слабші. Немає циклічної залежності. Наприклад: замовлення та продукт. Композиція- це взаємозв'язок, коли один клас вписується в колекцію. Це частина цілого відношення, коли частина не може існувати без цілого. Якщо ціле знищується, всі його складові також будуть знищені. Це сильніші стосунки. Наприклад: багатокутник та його вершини, замовлення та його компонент.

s_a_p 20 серпня 2008 о 19:09

Поліморфізм для початківців

  • PHP

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

Постановка задачі

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

Найкращі прості варіанти, які приходять на думку — написати три окремі класи і працювати з ними. Або написати один клас, в якому будуть всі властивості, притаманні всім трьом типам публікацій, а будуть задіяні тільки потрібні. Але для різних типів аналогічні за логікою методи повинні працювати по-різному. Робити кілька однотипних методів для різних типів (get_news, get_announcements, get_articles) це вже зовсім неграмотно. Тут нам допоможе поліморфізм.

Абстрактний клас

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

abstract class Publication
{
// таблиця, у якій зберігаються дані щодо елементу
protected $table;

// властивості елемента нам невідомі
protected $properties = array();

// Конструктор

{
// Зверніть увагу, ми не знаємо, з якої таблиці нам потрібно отримати дані
$result = mysql_query ("SELECT * FROM `" . $this -> table . "` WHERE `id`="" . $id . "" LIMIT 1" );
// які ми отримали дані, ми також не знаємо
$this -> properties = mysql_fetch_assoc ($result);
}

//Метод, однаковий для будь-якого типу публікацій, повертає значення властивості
public function get_property ($name)
{
if (isset($this -> properties [ $name ]))
return $this -> properties [$name];

Return false;
}

// метод, однаковий будь-якого типу публікацій, встановлює значення властивості
public function set_property ($name, $value)
{
if (!isset($this -> properties [ $name ]))
return false;

$this -> properties [$name] = $value;

Return $value;
}

// а цей метод має надрукувати публікацію, але ми не знаємо, як саме це зробити, і тому оголошуємо його абстрактним
abstract public function do_print();
}

Похідні класи

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

class News extends Publication
{
// конструктор класу новин, похідного від класу публікацій
public function __construct ($id )
{
// встановлюємо значення таблиці, в якій зберігаються дані щодо новин
$this -> table = "news_table";
parent :: __construct ($id);
}

Public function do_print ()
{
echo $this -> properties ["title"];
echo "

" ;
echo $this -> properties ["text"];
echo "
Джерело: ". $this -> properties ["source"];
}
}

Class Announcement extends Publication
{
// конструктор класу оголошень, похідного від класу публікацій
public function __construct ($id )
{
// встановлюємо значення таблиці, де зберігаються дані по оголошенням
$this -> table = "announcements_table";
// Викликаємо конструктор батьківського класу
parent :: __construct ($id);
}

// Перевизначаємо абстрактний метод друку
public function do_print ()
{
echo $this -> properties ["title"];
echo "
Увага! Оголошення дійсне до "
. $this -> properties ["end_date"];
echo "

$this -> properties [ "text" ];
}
}

Class Article extends Publication
{
// конструктор класу статей, похідного від класу публікацій
public function __construct ($id )
{
// встановлюємо значення таблиці, де зберігаються дані по статтям
$this -> table = "articles_table";
// Викликаємо конструктор батьківського класу
parent :: __construct ($id);
}

// Перевизначаємо абстрактний метод друку
public function do_print ()
{
echo $this -> properties ["title"];
echo "

" ;
echo $this -> properties ["text"];
echo "
$this -> properties [ "author" ];
}
}

Тепер про використання

Суть у тому, що той самий код використовується для об'єктів різних класів.

// Наповнюємо масив публікацій об'єктами, похідними від Publication
$publications = New News ($news_id);
$publications = New Announcement ($announcement_id );
$publications = new Article ($article_id );

Foreach ($publications as $publication ) (
// якщо ми працюємо зі спадкоємцями Publication
if ($publication instanceof Publication ) (
// то друкуємо дані
$ publication -> do_print ();
) else (
// Виняток або обробка помилки
}
}

От і все. Легким рухом руки штани перетворюються на елегантні шорти:-).

Основна вигода поліморфізму — легкість, з якою можна створювати нові класи, що «ведуть себе» аналогічно спорідненим, що, у свою чергу, дозволяє досягти розширюваності та модифікованості. У статті показано лише примітивний приклад, але навіть у ньому видно, наскільки використання абстракцій може полегшити розробку. Ми можемо працювати з новинами точно так, як із оголошеннями чи статтями, при цьому нам навіть не обов'язково знати, з чим ми працюємо! У реальних, набагато складніших додатках, ця вигода ще більш відчутна.

Трохи теорії

  • Методи, що потребують перевизначення, називаються абстрактними. Логічно, якщо клас містить хоча б один абстрактний метод, він теж є абстрактним.
  • Очевидно, що об'єкт абстрактного класу неможливо створити, інакше він не був би абстрактним.
  • Похідний клас має властивості та методи, що належать базовому класу, та, крім того, може мати власні методи та властивості.
  • Метод, що перевизначається у похідному класі, називається віртуальним. У базовому абстрактному класі про цей метод немає жодної інформації.
  • Суть абстрагування в тому, щоб визначати метод у тому місці, де є найбільш повна інформаціяпро те, як він має працювати.
UPD:з приводу sql-inj і порушення MVC - панове, це просто приклад, причому приклад з поліморфізму, в якому я не вважаю за потрібне приділяти значення цим речам. Це тема для інших статей.