Java. Об’єктно-орієнтоване програмування з інтерфейсами

      Copyright (c)  2001  Alexandre Moskovskikh.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.1
or any later version published by the Free Software Foundation;
with no Invariant Sections, with no Front-Cover Texts,
and with no Back-Cover Texts.
A copy of the license is included in the section entitled “GNU
Free Documentation License”.

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

Питання, зауваження та пропозиції вітаються.
mailto: mosk@chat.ru
WWW: mosk.chat.ru

Історія

GNU Free Documentation License
Введення
1. Декларація типу
2. Реалізація типу
3. Спадкування типу
4. Узагальнення

Введення

“У вузькому сенсі слова Java – це об’єктно-орієнтована мова,нагадує C + +, але більш простий для освоєння та використання.У більш широкому сенсі Java – це ціла технологія програмування,спочатку розрахована на інтеграцію з Web-сервісом. …Java-середу повинна бути як можна більш мобільної, в ідеалі повністюнезалежною від платформи. “
Java як центр архіпелагу. Олександр Таранов, Володимир Цішевскій

Наведена цитата типова для статей, присвячених Java.Все сказане в ній – правда. Але не вся.

Java – це не тільки “С + + без покажчиків” і не тільки “С + + для інтернету”.Java – це об’єктно-орієнтована мова нового покоління.

Усвідомлення цього факту зажадало від мене перегляду стереотипів,сформованих під час програмування на С + +.Цей процес я планую відобразити в серії статей “ООП на Java”.

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

Передбачувані чергові теми – множинне спадкування,динамічні типи, узагальнення і класифікація, відображення на РСУБД.

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

Хороший огляд Java (особливо для знайомих з C + +) можна знайти в статті
Java як центр архіпелагу. Олександр Таранов, Володимир Цішевскій
infocity.kiev.ua,розділ Програмування – Java

Краща книга по Java Брюс Еккель. Філософія Java.Оригінал можна скачати за адресою:
http://www.mindview.net/
Bruce Eckel: Thinking in Java 2nd Edition
.

Перше питання, що виник у мене при знайомстві з Java,
Навіщо потрібні інтерфейси?Здавалося б цілком достатньо звичайних і абстрактних класів у стилі C + +.Для маскування відсутності множинного спадкоємства? Смішно.

Після деякої кількості роздумів і особливо після неодноразовогопрочитання згаданої книги Брюса Еккель питання трансформувався:
Навіщо в Java потрібні класи?

Звичні варіанти відповіді: клас вводить новий тип, клас узагальнює ….

1. Декларація типу

Новий тип вводиться специфікацією інтерфейсу.

У C + + клас неявно визначає інтерфейс.І в силу цього одночасно оголошує тип.При цьому єдиний інтерфейс зв’язується з єдиною реалізацією.Множинне успадкування і абстрактні класи в C + + – це перш за всеспроба обійти жорстку детермінованість.

У Java подібного обмеження немає.Будь інтерфейс (тип) може мати багато реалізацій.Будь-який клас може реалізовувати багато інтерфейсів.

Як приклад спробуємо оголосити власний тип “число”.Для стислості обмежимося операціями додавання і множення.


/* INumber.java —————————————————— (8<  *  * Декларація типу INumber і фрагмент програми, що використовує цей тип.  *  * ------------------------------------------------- ------------------- * / interface INumber {   public void setValue (String s);   public INumber add (INumber n);   public INumber mul (INumber n);   public String toString (); } class CalcNumber {   void calculation (INumber n1, INumber n2, INumber n3)   {     INumber xx;     xx = n2; / / Якщо закоментувати попередній рядок, то компілятор видасть помилку: / / Variable xx might not have been initialized     xx.setValue ("5.3"); System.out.println("xx="+xx.toString()); n1.setValue("21"); n2.setValue("37.6"); System.out.println("n1="+n1.toString()); System.out.println("n2="+n2.toString()); System.out.println("n3="+n3.toString()); System.out.println("(n1+n2)*n3=" + n1.add(n2).mul(n3).toString()); n1.setValue("21"); System.out.println("(n2+n1)*n3=" + n2.add(n1).mul(n3).toString()); n2.setValue("37.6"); System.out.println("n1*(n2+n3)=" + n1.mul(n2.add(n3)).toString()); n1.setValue("21"); n2.setValue("37.6"); System.out.println("n3*(n1+n2)=" + n3.mul(n1.add(n2)).toString()); } } // (8<

З наведеного прикладу видно:

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

Додамо два варіанти реалізації.


/* DblNumber.java —————————————————- (8<  *  * Реалізація типу INumber через double.  *  * ------------------------------------------------- ------------------- * / class DblNumber implements INumber {   double d;   public DblNumber (double ip)   {     d = ip;   }   public void setValue (String s)   {     d = (new Double (s)). doubleValue ();   }   public INumber add (INumber n)   {     d + = (new Double (n.toString ())). doubleValue ();     return this;   }   public INumber mul (INumber n)   {     d * = (new Double (n.toString ())). doubleValue ();     return this;   }   public String toString ()   {     return (new Double (d)). toString ();   } } / / (8 <


/* IntNumber.java —————————————————- (8<  *  * Реалізація типу INumber через int.  *  * ------------------------------------------------- ------------------- * / class IntNumber implements INumber {   int i;   public IntNumber (int v)   {     i = v;   }   public void setValue (String s)   {     String sw = s;     int l = sw.indexOf ('.'); if (l > 0)
sw = sw.substring(0, l);
i = (new Integer(sw)).intValue();
}
public INumber add(INumber n)
{
String sw = n.toString();
int l = sw.indexOf(‘.’);
if (l > 0)
sw = sw.substring(0, l);
i += (new Integer(sw)).intValue();
return this;
}
public INumber mul(INumber n)
{
String sw = n.toString();
int l = sw.indexOf(‘.’);
if (l > 0)
sw = sw.substring(0, l);
i *= (new Integer(sw)).intValue();
return this;
}
public String toString()
{
return (new Integer(i)).toString();
}
} // (8<

Перевіримо результат:


/* TestNumber.java ————————————————— (8<  *  * Тестування типу INumber.  *  * ------------------------------------------------- ------------------- * / public class TestNumber {   public static void main (String [] args) {     INumber i1 = new IntNumber (22);     INumber i2 = new DblNumber (11.2);     INumber i3 = new DblNumber (3.4);     CalcNumber cn = new CalcNumber ();     cn.calculation (i1, i2, i3);   } } / / (8 <

Результат виконання тестової програми:


xx=5.3
n1=21
n2=37.6
n3=3.4
(n1+n2)*n3=174
(n2+n1)*n3=199.24
n1*(n2+n3)=861
n3*(n1+n2)=197.2

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

Цікаво відзначити, що результат операції надINumber залежить від послідовності використання змінних.Ефект виникає тому, що в специфікації типу ми опустиливажливі для чисел властивості: точність і діапазон допустимих значень.В результаті вони неявно беруться з базового типу,використаного при реалізації.В даному випадку достатньо додати метод
setFormat(maxValue, minValue, decimal).

2. Реалізація типу

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

У наступному прикладі є тільки один клас – для запуску програми.Власне логіка програми реалізована без використання класів!


/* TestAnimal.java ————————————————— (8<  *  * Зразок безкласової реалізації  *  * ------------------------------------------------- ------------------- * / import java.util.ArrayList; interface Animal {   void giveSignals ();   void goHome ();   String getTitle ();   String getNick (); } interface Command {   void exeCommand (Animal an); } interface Ranch {   void add (Animal an);   void visitAll (Command cmd); } public class TestAnimal {   public static void main (String [] args)   {      Ranch myRanch = new Ranch ()      {        private ArrayList ranchAnimals = new ArrayList ();        public void add (Animal a)        {          ranchAnimals.add (a);        }               public void visitAll (Command cmd)        {          for (int i = 0; i >>>>>>>>\n”);
myRanch.visitAll(new Command()
{
public void exeCommand(Animal a)
{System.out.print (a.getTitle () + “” + a.getNick () + “говорить:”);
a.giveSignals();
}
});
// go to Home
System.out.println(“\n<<<<<<< Всі додому! >>>>>>>>>\n”);
myRanch.visitAll(new Command()
{
public void exeCommand(Animal a)
{System.out.print (a.getTitle () + “” + a.getNick () + “йде додому:”);
a.goHome();
}
});
}
} // (8<

Використання класу Sheep дозволило б скоротити текст програми.Ніяких інших переваг введення цього класу не дає.Для інших об’єктів визначення відповідних класів не дає нічого.

Результат виконання програми:


<<<<<<< Всі подали голос>>>>>>>>>собака Блек говорить: Гав-гаввівця говорить: Бе-евівця говорить: Бе-е
<<<<<<< Всі додому! >>>>>>>>>собака Блек йде додому: Біжить в будкувівця йде додому: Йде в загородувівця йде додому: Йде в загороду

Хтось скаже, що в наведеному прикладі використаноанонімні класи і буде правий.

Але що таке анонімний клас?У специфікації Java сказано: декларація анонімного класу автоматичновитягується компілятором з виразу створення екземпляра класу.Тобто автори мови скористалися принципом чайника і привели задачустворення “самовизначення” об’єкта до вже вирішеною.Іншими словами, зазвичай спочатку декларується клас,а потім породжується його примірник.З анонімним класом все навпаки – спочатку описується екземпляр,а потім під нього підганяється клас.Реінжиніринг називається. 🙂

Можна сказати, що анонімний клас потрібен для того,щоб узаконити існування створеного об’єкта.

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

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

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

3. Спадкування типу

Спадкування типу і поліморфізм забезпечуються успадкуваннямінтерфейсу і нічим іншим.

Простий приклад:


/* TestShips.java —————————————————- (8<  *  * Спадкування інтерфейсів і поліморфізм  *  * ------------------------------------------------- ------------------- * / import java.util.ArrayList; interface Ship {   void runTo (String s); } interface WarShip extends Ship {   void bombard (); } interface Transport extends Ship {   void loadTroops (int n);   void landTroops (); } public class TestShips {   public static void main (String [] args)   {      ArrayList ships = new ArrayList ();      for (int i = 0; i <3; i + +)        ships.add (new Transport ()          {            private int troopers;            public void runTo (String s) {System.out.println ("Транспорт прямує в "+s+"."); } public void loadTroops(int n) { troopers = n; } public void landTroops() { System.out.println((new Integer(troopers)).toString()+" загонів десантувалися. "); } } ); for(int i = 0; i < 2; i++) ships.add(new WarShip() { public void runTo(String s) { System.out.println("Корабель прямує в "+s+"."); } public void bombard() { System.out.println("Корабель бомбардує мета. "); } } ); for(int i = 0; i < 3; i++) ((Transport)ships.get(i)).loadTroops(i+5); for(int i = 0; i < ships.size(); i++) ((Ship)ships.get(i)).runTo("Вражий Порт "); for(int i = 0; i < 3; i++) ((Transport)ships.get(i)).landTroops(); for(int i = 3; i < ships.size(); i++) ((WarShip)ships.get(i)).bombard(); // Run-time error: java.lang.ClassCastException // ((Transport)ships.get(4)).landTroops(); // Run-time error: java.lang.ClassCastException // ((WarShip)ships.get(1)).bombard(); // Compile-time error: cannot resolve symbol // ((Ship)ships.get(1)).landTroops(); // ((Ship)ships.get(4)).bombard(); } } // (8<

Результат виконання програми:

Транспорт направляється в вражою Порт.Транспорт направляється в вражою Порт.Транспорт направляється в вражою Порт.Корабель прямує в вражою Порт.Корабель прямує в вражою Порт.5 загонів десантувалися.6 загонів десантувалися.7 загонів десантувалися.Корабель бомбардує мета.Корабель бомбардує мета.

Концепція інтерфейсів додає поліморфізму другий вимір:

Спадкування має два аспекти:

Спадкування реалізації НЕ означає спадкування типу!У практиці це не зустрічається, тому що і в С + + і в Java неможливоспадкування реалізації без успадкування інтерфейсу.У C + + інтерфейс і клас невіддільні одне від одного.У Java інтерфейс від класу відокремити можна, але клас від інтерфейсу – не можна.

У С + + і в Java сукупність загальнодоступних (public) методівнеявно утворює інтерфейс даного класу.У силу цього спадкування класу автоматично означає як успадкуванняреалізації, так і спадкування інтерфейсу (типу).Очевидно, що спадкування структури даних і програмного коду не визначаєтип нащадка.Наприклад, абстрактні методи є частиною інтерфейсу і не є частиноюреалізації.Якби можна було виключити їх з успадкування, то ми отримали бспадкування реалізації без збереження типу.

Зверніть увагу, що в DblNumber і IntNumberуспадкування реалізації немає. Тому ієрархія класів не використовується.

4. Узагальнення

Що ж залишилося на частку класу? – Узагальнення.

Точніше, узагальнення реалізації.

А якщо бути чесним до кінця – організація повторно використовуваного коду.

Можливе повторне використання:

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

Таким чином, істинне призначення класу – упаковка повторновикористовуваного коду згідно з принципами об’єктно-орієнтованоїтехнології.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*