Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка КР.doc
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
185.34 Кб
Скачать

5.3. Інтерфейси

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

Java використовує так званий інтерфейс - деяка подоба класу, де методи лише оголошуються, але не визначаються. Розробник інтерфейсу вирішує, які методи повинні підтримуватися в класах, що реалізують цей інтерфейс, і що ці методи повинні робити. Розглянемо використання інтерфейсу при реалізації завдання «Хор тварин»:

interface Voice{

void voice(); 

}

class Dog implements Voice{

  public void voice (){

    System.out.println("Gav-gav!");

  } 

}

class Cat implements Voice{

  public void voice (){

    System.out.println("Miaou!");

  } 

}

class Cow implements Voice{ 

  public void voice(){

    System.out.println("Mu-u-u!"); 

  }

}

public class Chorus{

  public static void main(String[] args){ 

    Voiced singer = new Voice[3]; 

    singer[0] = new Dog(); 

    singer[1] = new Cat(); 

    singer[2] = new Cow(); 

    for(int i = 0; i < singer.length; i++)

      singer[i].voice();

  }

}

Слід зазначити, що ми можемо реалізувати даний приклад і за допомогою абстрактних класів:

abstract class Pet{

   abstract void voice(); 

}

class Dog extends Pet{

   void voice(){

      System.out.printin("Gav-gav!");

   }

}

class Cat extends Pet{

   void voice () {

      System.out.printin("Miaou!"); 

   }

}

class Cow extends Pet{ 

   void voice(){

      System.out.printin("Mu-u-u!");

   }

}

public class Chorus(

   public static void main(String[] args){ 

      Pet[] singer = new Pet[3]; 

      singer[0] = new Dog(); 

      singer[1] = new Cat(); 

      singer[2] = new Cow(); 

      for (int i = 0; i < singer.length; i++)

         singer[i].voice();

   }

}

Що ж краще використовувати: абстрактний клас або інтерфейс? На це питання немає однозначної відповіді. Створюючи абстрактний клас, ви волею-неволею занурюєте його в ієрархію класів, пов'язану умовами одиночного наслідування і єдиним предком - класом object. Користуючись інтерфейсами, ви можете вільно проектувати систему, не замислюючись про ці обмеження. З іншого боку, в абстрактних класах можна відразу реалізувати частину методів. Реалізуючи ж інтерфейси, ви приречені на нудне перевизначення всіх методів.

Концепція абстрактних методів і інтерфейсів дозволяє запропонувати альтернативу множинного успадкування. У Java клас може мати тільки одного батька, оскільки при множинному спадкуванні можуть виникати конфлікти, які серйозно заплутують об'єктну модель. Наприклад, якщо у класу є два батьківських, які мають однаковий метод з різною реалізацією, то який з них успадкує новий клас? І як буде працювати функціональність батьківського класу, який позбувся свого методу?

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

Саме так влаштовані інтерфейси в Java. Від них не можна породжувати об'єкти, але інші класи можуть реалізовувати їх.

Але що робити, якщо все-таки при породженні треба використовувати кілька предків? Наприклад, у нас є спільний клас автомобілів Automobile, від якого можна породити клас вантажівок Truck і клас легкових автомобілів Саг. Але от треба описати пікап Pickup. Цей клас повинен успадковувати властивості і вантажних, і легкових автомобілів. У таких випадках і слід застосовувати конструкцію мови Java під назвою інтерфейс.

Ось яку схему можна запропонувати для ієрархії автомобілів:interface Automobile{ . . . }

interface Car extends Automobile{ . . . }

interface Truck extends Automobile{ . . . } 

interface Pickup extends Car, Truck{ . . . }

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

Використовувати потрібно не інтерфейс, а його реалізацію (implementation). Реалізація інтерфейсу - це клас, в якому розписуються методи одного або декількох інтерфейсів. У заголовку класу після його імені або після імені його суперкласу, якщо він є, записується слово implements і, через кому, перераховуються імена інтерфейсів.

Ось як можна реалізувати ієрархію автомобілів:

interface Automobile{ . . . }

interface Car extends Automobile{ . . . }

class Truck implements Automobile{ . . . }

class Pickup extends Truck implements Car{ . . . }

или так:

interface Automobile{ . . . } 

interface Car extends Automobile{ . . . } 

interface Truck extends Automobile{ . . . } 

class Pickup implements Car, Truck{ . . . }

Реалізація інтерфейсу може бути неповною, деякі методи інтерфейсу реалізовані, а інші - ні. Така реалізація - абстрактний клас, його обов'язково треба помітити модифікатором abstract. Як реалізувати в класі Pickup метод f (), описаний і в інтерфейсі Car, і в інтерфейсі Truck з однаковою сигнатурою? Відповідь проста - ніяк. Таку ситуацію не можна реалізувати в класі Pickup. Програму треба спроектувати по-іншому.

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

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

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

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

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

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

Розглянемо таке можливе властивість живого організму, як здатність харчуватися комахами. Очевидно, що це властивість можна приписати всій групі птахів або ссавців, а тим більше рослин. Але існують представники кожної такої групи, які цим властивістю володіють - для рослин це росичка, для птахів, наприклад, ластівки, а для ссавців - мурахоїди. Причому, очевидно, "реалізовано" це властивість у кожного виду зовсім по-різному. Можна було б оголосити відповідний метод (скажімо, consumeInsect (Insect)) в кожного представника незалежно. Але якщо завдання полягає в моделюванні, наприклад зоопарку, то однотипну процедуру - годування комахами - довелося б описувати для кожного виду незалежно, що суттєво ускладнило б код, причому без будь-якої користі. Java пропонує інше рішення. Оголошується інтерфейс InsectConsumer:

public interface InsectConsumer {

void consumeInsect(Insect i);

}

Його реалізують усі відповідні тварини і рослини:

/ / Росички розширює клас Рослина

public class Sundew extends Plant implements InsectConsumer {

public void consumeInsect(Insect i) {

...

}

}

/ / Ластівка розширює клас Птах

public class Swallow extends Bird implements InsectConsumer {

public void consumeInsect(Insect i) {

...

}

}

/ / Мурахоїд розширює клас Ссавець

public class AntEater extends Mammal implements InsectConsumer {

public void consumeInsect(Insect i) {

...

}

}

В результате в классе, моделирующем служащего зоопарка, можно объявить соответствующий метод:

// Службовець, який відповідає за годування, розширює клас Службовець

class FeedWorker extends Worker {

// За допомогою цього методу можна нагодувати

// І росичку, і ластівку, і мурахоїда

public void feedOnInsects(InsectConsumer consumer) {

...

consumer.consumeInsect(insect);

...

}

}

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

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

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

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