35

35


class Upcase extends StringProcessor {
public String process(Object input) { II Ковариантный возвращаемый тип return ((String)input) .toUpperCaseO;
class Downcase extends StringProcessor { public String process(Object input) {
return ((String)input).toLowerCase();
class Splitter extends StringProcessor {

public String process(Object input) {
return Arrays.toString(((String)input).split(" ")).
}
} /* Output
Используем Processor Upcase
IF SHE WEIGHS THE SAME AS A DUCK, SHE'S MADE OF WOOD Используем Processor Downcase if she weighs the same as a duck, she's made of wood Используем Processor Splitter
[If. she. weighs, the. same. as. a. duck., she's, made. of. wood] *///:-
Впрочем, довольно часто модификация тех классов, которые вы собираетесь использовать, невозможна. Например, в примере с электронными фильтрами библиотека была получена из внешнего источника. В таких ситуациях приме­няется паттерн «адаптер»: вы пишете код, который получает имеющийся интер­фейс, и создаете тот интерфейс, который вам нужен:
//: interfaces/interfaceprocessor/FilterProcessor java package interfaces interfaceprocessor, import interfaces.filters.*;
class FilterAdapter implements Processor { Filter filter.
public FilterAdapter(Filter filter) { this.filter = filter.
}
public String nameO { return filter.nameO; } public Waveform process(Object input) {
return filter.process((Waveform)input).
}
}
public class FilterProcessor {
public static void main(String[] args) { Waveform w = new Waveform(); -
Apply process(new FilterAdapter(new LowPass(l.O)), w); Apply.process(new FilterAdapter(new HighPass(2.0)). w); Apply.process(
new FilterAdapter(new BandPass(3.0. 4 0)). w);
}
} /* Output.
Используем Processor LowPass Waveform 0
Используем Processor HighPass Waveform 0
Используем Processor BandPass
Waveform 0 *///.-
Конструктор FilterAdapter получает исходный интерфейс (Filter) и создает объект с требуемым интерфейсом Processor. Также обратите внимание на при­менение делегирования в классе FilterAdapter.
Отделение интерфейса от реализации позволяет применять интерфейс к разным реализациям, а следовательно, расширяет возможности повторного использования кода.
«Множественное наследование» в Java
Так как интерфейс по определению не имеет реализации (то есть не обладает памятью для хранения данных), нет ничего, что могло бы помешать совмеще­нию нескольких интерфейсов. Это очень полезная возможность, так как в неко­торых ситуациях требуется выразить утверждение: «Икс являетсяиА,иБ,иВ од­новременно». В С++ подобное совмещение интерфейсов нескольких классов называетсямножественным наследованием, и оно имеет ряд очень неприятных аспектов, поскольку каждый класс может иметь свою реализацию. В Java мож­но добиться аналогичного эффекта, но, поскольку реализацией обладает всего один класс, проблемы, возникающие при совмещении нескольких интерфейсов в С++, в Java принципиально невозможны:



При наследовании базовый класс вовсе не обязан быть абстрактным или «реальным» (без абстрактных методов). Если наследованиедействительноосу­ществляется не от интерфейса, то среди прямых «предков» класс может быть только один — все остальные должны быть интерфейсами. Имена интерфейсов перечисляются вслед за ключевым словом implements и разделяются запятыми. Интерфейсов может быть сколько угодно, причем к ним можно проводить вос­ходящее преобразование. Следующий пример показывает, как создать новый класс на основе реального класса и нескольких интерфейсов:
//: interfaces/Adventure java
// Использование нескольких интерфейсов.
interface CanFight { void fightO,
}
interface CanSwim { void swimO,
}
interface CanFly { void fly().
}
class ActionCharacter {
public void fightO {}
}
class Hero extends ActionCharacter
implements CanFight, CanSwim, CanFly { public void swimO {}
public void fly() {}
}
public class Adventure {
public static void t(CanFight x) { x fightO; } public static void u(CanSwim x) { x swimO, } public static void v(CanFly x) { x fly(); } public static void w(ActionCharacter x) { x.fightO. } public static void main(String[] args) { Hero h = new HeroO;
t(h), // Используем объект в качестве типа CanFight u(h). // Используем объект в качестве типа CanSwim v(h). // Используем объект в качестве типа CanFly w(h), // Используем объект в качестве ActionCharacter
}
} ///-
Мы видим, что класс Него сочетает реальный класс ActionCharacter с интер­фейсами CanFight, CanSwim и CanFly. При объединении реального класса с интер­фейсами на первом месте должен стоять реальный класс, а за ним следуют ин­терфейсы (иначе компилятор выдаст ошибку).
Заметьте, что объявление метода fight() в интерфейсе CanFight совпадает с тем, что имеется в классе ActionCharacter, и поэтому в классе Негонетопреде­ления метода fight(). Интерфейсы можно расширять, но при этом получается другой интерфейс. Необходимым условием для создания объектов нового типа является наличие всех определений. Хотя класс Него не имеет явного определе­ния метода fight(), это определение существует в классе ActionCharacter, что и де­лает возможным создание объектов класса Него.
Класс Adventure содержит четыре метода, которые принимают в качестве ар­гументов разнообразные интерфейсы и реальный класс. Созданный объект Него передается всем этим методам, а это значит, что выполняется восходящее пре­образование объекта к каждому интерфейсу по очереди. Система интерфейсов Java спроектирована так, что она нормально работает без особых усилий со сто­роны программиста.
Помните, что главная причина введения в язык интерфейсов представлена в приведенном примере: это возможность выполнять восходящее преобразова­ние к нескольким базовым типам. Вторая причина для использования интер­фейсов совпадает с предназначением абстрактных классов: запретить програм- мисту-клиенту создание объектов этого класса.
Возникает естественный вопрос: что лучше — интерфейс или абстрактный класс? Если можно создать базовый класс без определений методов и перемен­ных-членов, выбирайте именно интерфейс, а не абстрактный класс. Вообще го­воря, если известно, что нечто будет использоваться как базовый класс, первым делом постарайтесь сделать это «нечто» интерфейсом.
Расширение интерфейса через наследование
Наследование позволяет легко добавить в интерфейс объявления новых мето­дов, а также совместить несколько интерфейсов в одном. В обоих случаях полу­чается новый интерфейс, как показано в следующем примере:
//• interfaces/HorrorShow java
// Расширение интерфейса с помощью наследования
interface Monster { void menace(),
}
interface DangerousMonster extends Monster { void destroy();
}
interface Lethal { void kill();
}
class DragonZilla implements DangerousMonster { public void menaceО {} public void destroyО {}
}
interface Vampire extends DangerousMonster, Lethal { void drinkBloodO;
}
class VeryBadVampire implements Vampire { public void menaceO {} public void destroyО {} public void killO {} public void drinkBloodO {}
}
public class HorrorShow {
static void u(Monster b) { b.menaceO; } static void v(DangerousMonster d) { d. menaceO, d.destroyО;
}
static void w(Lethal 1) { 1 killO; } public static void main(String[] args) {
DangerousMonster barney = new DragonZi11a(); u(barney); v(barney);
Vampire vlad = new VeryBadVampire(); u(vlad), v(vlad); w(vlad);
}
} ///:-
DangerousMonster представляет собой простое расширение Monster, в резуль­тате которого образуется новый интерфейс. Он реализуется классом DragonZilla.

Синтаксис, использованный в интерфейсе Vampire, работаеттолькопри на­следовании интерфейсов. Обычно ключевое слово extends может использо­ваться всего с одним классом, но, так как интерфейс можно составить из несколь­ких других интерфейсов, extends подходит для написания нескольких имен интерфейсов при создании нового интерфейса. Как нетрудно заметить, имена нескольких интерфейсов разделяются при этом запятыми.
Конфликты имен при совмещении интерфейсов
При реализации нескольких интерфейсов может возникнуть небольшая про­блема. В только что рассмотренном примере интерфейс CanFight и класс Action- Character имеют идентичные методы void fight(). Хорошо, если методы полно­стью тождественны, но что, если они различаются по сигнатуре или типу возвращаемого значения? Рассмотрим такой пример:
//• i interfaces/InterfaceColli si on java package interfaces;
interface  II { void f(); }
interface  12 { int f(int i); }
interface  13 { int f(). }
class С {   public int f() { return 1; } }
class C2 implements II. 12 { public void f() {}
public int f(int i) { return 1; } // перегружен
}
class C3 extends С implements 12 {
public int f(int i) { return 1; } // перегружен
}
class C4 extends С implements 13 { // Идентичны, все нормально; public int f() { return 1; }
}
// Методы различаются только по типу возвращаемого значения; //! class С5 extends С implements II {} //! interface 14 extends II. 13 {} ///;-
Трудность возникает из-за того, что переопределение, реализация и пере­грузка образуют опасную «смесь». Кроме того, перегруженные методы не могут различаться только возвращаемыми значениями. Если убрать комментарий в двух последних строках программы, сообщение об ошибке разъясняет суть происходящего:
InterfaceCollisi on.java.23 f() в С не может реализовать f() в II; попытка использовать несовместимые возвращаемые типы обнаружено: int требуется- void
InterfaceCollisi on java;24- интерфейсы 13 и II несовместимы; оба определяют f(). но с различными возвращаемыми типами
Использование одинаковых имен методов в интерфейсах, предназначенных для совмещения, обычно приводит к запутанному и трудному для чтения коду. Постарайтесь по возможности избегать таких ситуаций.
Интерфейсы как средство адаптации
Одной из самых убедительных причин для использования интерфейсов являет­ся возможность определения нескольких реализаций для одного интерфейса. В простых ситуациях такая схема принимает вид метода, который при вызове передается интерфейсу; от вас потребуется реализовать интерфейс и передать объект методу.
Соответственно, интерфейсы часто применяются в архитектурном паттерне«Стратегия».Вы пишете метод, выполняющий несколько операций; при вызо­ве метод получает интерфейс, который тоже указываете вы. Фактически вы го­ворите: «Мой метод может использоваться с любым объектом, удовлетворяю­щим моему интерфейсу». Метод становится более гибким и универсальным.
Например, конструктор класса Java SE5 Scanner получает интерфейс Readable. Анализ показывает, что Readable не является аргументом любого дру­гого метода из стандартной библиотеки Java — этот интерфейс создавался ис­ключительно для Scanner, чтобы его аргументы не ограничивались определен­ным классом. При таком подходе можно заставить Scanner работать с другими типами. Если вы хотите создать новый класс, который может использоваться со Scanner, реализуйте в нем интерфейс Readable:
//. interfaces/RandomWords java
// Реализация интерфейса для выполнения требований метода
import java nio.*;
import java util.*,
public class RandomWords implements Readable { private static Random rand = new Random(47); private static final char[] capitals =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); private static final char[] lowers =
"abcdefghijklmnopqrstuvwxyz".toCharArrayO; private static final char[] vowels =
"aeiou" toCharArrayO; private int count,
public RandomWords(int count) { this.count = count: } public int read(CharBuffer cb) { if(count-- == 0)
return -1; // Признак конца входных данных cb.append(capi tals[rand.nextInt(capi ta1s.1 ength) ]); for(int i = 0; i < 4; i++) {
cb.append(vowels[rand.nextInt(vowels.1ength)]); cb append(lowers[rand.nextInt(lowers.length)]),
}
cb.append(" "),
return 10; // Количество присоединенных символов
}
public static void main(String[] args) {
Scanner s = new Scanner(new RandomWords(10)); while(s.hasNextO)
System.out.println(s.nextO);
}
} /* Output:
Yazeruyac
Fowenucor Goeazimom Raeuuacio Nuoadesiw Hageaikux Ruqicibui. Numasetih Kuuuuozog Waqizeyoy */// ~
Интерфейс Readable требует только присутствия метода read(). Метод read() либо добавляет данные в аргумент CharBuffer (это можно сделать несколькими способами; обращайтесь к документации CharBuffer), либо возвращает -1 при отсутствии входных данных.

Report Page