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

Года три назад я кому-то рассказывал о только что прочтенной статье с описанием проекта расширения (GNU) C++. В ней упоминалось новое понятие: сигнатура. Позже, попытавшись подробно объяснить проект, я начал путаться и осознал, что не полностью понял статью.

Когда в Java стали использоваться интерфейсы и я стал разбираться в их работе, я понял, насколько они схожи с сигнатурами, описанными в публикации трехлетней давности. У них та же цель - создание типовой модели безопасности без жесткой наследственной привязки. Полностью познав назначение Java-интерфейсов, я буквально влюбился в них. И теперь, после двух лет работы с этими интерфейсами, я все еще их люблю, но уже более осознанно.

Мне удалось выявить ряд проблем, связанных с интерфейсами:

1. Значительное увеличение размера пакетов

2. Невозможность перехода к новым версиям

3. Область видимости имен

4. Отсутствие реализаций по умолчанию

Увеличение размера пакетов

Первая проблема вызвана взрывным ростом числа классов и интерфейсов. Я просто загрузил вторую бета-версию JDK 1.2 и сразу же просмотрел характеристики пакета. Прежде всего бросается в глаза обилие пакетов и классов. До обещанной в JDK 1.02 простоты оказалось еще весьма далеко.

Изучив интерфейсы внимательнее, я обнаружил, что объем Java 1.2 значительно увеличивается за счет введения новой облегченной структуры интерфейса под названием Swing, дополняющей инструментарий Abstract Windowing Toolkit (AWT). Одна из причин тому, как ни странно, - интерфейс. Он по меньшей мере вдвое увеличивает число классов и средств сопряжения. Разработчики Java 1.2, похоже, взяли за правило использовать интерфейсы везде, где можно. Значительно увеличивая гибкость, данный подход все же сильно усложняет концептуальную модель разработки. Однако, чтобы продукт принес пользу, он должен реализоваться, а это, в свою очередь, приводит к появлению класса с именем, похожим на имя интерфейса. Лично мне такое стремительное разрастание не по нраву, так как при этом все значительно усложняется.

Невозможность расширения в будущем

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

public interface I {
	public void f();
}

Теперь, в следующей версии мы хотим расширить его новым методом:

public interface I {
	public void f();
	public void g();
}

При компиляции класса, реализующего первый интерфейс, с обновленным интерфейсом код разрушается.

Область видимости имен

Еще одна проблема заключается в области видимости имен методов в описании интерфейса. Класс, реализующий интерфейс с функцией f(), не может реализовать еще один интерфейс с функцией f(). Но что делать, если интерфейсы перекрываются по именам своих функций? Вы можете принять меры предосторожности, используя более длинные и образные имена, но такой подход часто делает интерфейс неудобочитаемым. Например, недавно я работал с Service-интерфейсом, в котором start() and stop() предлагались для использования в качестве логических имен. Я заменил имена на startService() и stopService(), чтобы разрешить реализацию этого интерфейса службам, унаследованным от класса Thread. В классе Thread существуют два метода с именами start() и stop(). Они предназначены для управления состоянием процесса. Подклассы класса Thread не могут реализовать ни один интерфейс, использующий методы с именами start и stop. В Java нет операторов, обеспечивающих концепцию локальной видимости имен, как это сделано в С++ (the ::). Такие операторы обязаны указывать компилятору, что делать в случае перекрытия имен методов из различных интерфейсов или надклассов.

Отсутствие реализаций по умолчанию

И последняя из выявленных проблем - это отсутствие реализаций по умолчанию. В случае, когда один класс расширяет другой, последний способен обеспечить реализацию всех методов по умолчанию. Иногда подкласс создается простым расширением надкласса и путем использования полной реализации надкласса. Некоторые интерфейсы довольно велики и реализация всех методов может оказаться весьма трудоемкой. Это особенно раздражает, когда полезными в определенных ситуациях оказываются реализации всего одного или двух методов. Концепция интерфейса заставляет пользователя вводить в описание каждый метод. Например, в интерфейсе Swing ButtonModel число определенных методов достигло 21 (!). Довольно часто при внедрении желательно реализовать лишь часть интерфейса и такая возможность должна существовать.

Решение проблем

Как же разрешить эти проблемы? Любые работы в данном направлении должны проводиться с величайшей осторожностью. К счастью, Sun соблюдает ее при внесении в язык каких-либо изменений, дабы избежать неприятностей, подобных тем, которые произошли с процедурой стандартизации С++. Однако поразмыслить над указанными проблемами можно, и без какого-либо вреда.

А как бы их разрешить и возможно ли такое? Просто добавьте в язык новое ключевое слово: "like", которое затем будет использоваться вместо "extends" и "interface":

public class C like A {
}

Этот код интерпретируется следующим образом: С должен обеспечивать тот же интерфейс, что и А. Однако, если С не реализует требуемый метод, реализация копируется из А. Ключевое слово "like" по существу означает: Трактовать класс как описание интерфейса и копировать код этого класса при отсутствии реализации (если нет абстрактного объекта).

Использование этого ключевого слова решает проблему дублирования классов и интерфейсов, а также проблемы реализации по умолчанию и разрастания, обсуждавшиеся выше. Мне кажется, что вполне возможно внести это изменение в текущую версию языка без изменения существующих виртуальных машин.

Заключение

Это лишь начало размышлений над проблемой интерфейсов. Я считаю, что их следует продолжить вплоть до полного разрешения, учитывая, разумеется, что изменения в языке Java должны выполняться крайне осторожно. И все же, уверяю вас, что несмотря на все сложности, я по-прежнему предан интерфейсам.


Питер Крейнс - консультант по программному обеспечению с большим опытом работы в области телекоммуникаций. В настоящее время он работает над вопросами сетевого управления для компании Ericsson Telecom. Последние полтора года Питер Крейнс активно работал с Java. С ним можно связаться по адресу peter.kriens@javaworld.com.