Выбрать главу

Все элементы, за исключением метода getCallbackReference(), в классе Callee2 являются закрытыми. Для любой связи с окружающим миром необходим ин­терфейс Incrementable. Здесь мы видим, как интерфейсы позволяют полностью отделить интерфейс от реализации.

Внутренний класс Closure просто реализует интерфейс Incrementable, предос­тавляя при этом связь с объектом Callee2 — но связь эта безопасна. Кто бы ни получил ссылку на Incrementable, он в состоянии вызвать только метод incre­mentO, и других возможностей у него нет (в отличие от указателя, с которым программист может вытворять все, что угодно).

Класс Caller получает ссылку на Incrementable в своем конструкторе (хотя пе­редача ссылки для обратного вызова может происходить в любое время), а по­сле этого использует ссылку для «обратного вызова» объекта Callee.

Главным достоинством обратного вызова является его гибкость — вы може­те динамически выбирать функции, выполняемые во время работы программы.

Внутренние классы и система управления

В качестве более реального пример использования внутренних классов мы рас­смотрим то, что я буду называть здесь системой управления (control frame­work).

Каркас приложения (application framework) — это класс или набор классов, разработанных для решения определенного круга задач. При работе с каркаса­ми приложений обычно используется наследование от одного или нескольких классов, с переопределением некоторых методов. Код переопределенных мето­дов адаптирует типовое решение, предоставляемое каркасом приложения, к ва­шим конкретным потребностям. Система управления представляет собой опре­деленный тип каркаса приложения, основным движущим механизмом которого является обработка событий. Такие системы называются системами, управляе­мыми по событиям (event-driven system). Одной из самых типичных задач в при­кладном программировании является создание графического интерфейса поль­зователя (GUI), всецело и полностью ориентированного на обработку событий.

Чтобы на наглядном примере увидеть, как с применением внутренних клас­сов достигается простота создания и использования библиотек, мы рассмотрим систему, ориентированную на обработку событий по их «готовности». Хотя в практическом смысле под «готовностью» может пониматься все, что угодно, в нашем случае она будет определяться по показаниям счетчика времени. Далее приводится общее описание управляющей системы, никак не зависящей от того, чем именно она управляет. Нужная информация предоставляется по­средством наследования, при реализации метода action().

Начнем с определения интерфейса, описывающего любое событие системы. Вместо интерфейса здесь используется абстрактный класс, поскольку по умол­чанию управление координируется по времени, а следовательно, присутствует частичная реализация:

//: innerclasses/control 1er/Event.java

// Общие для всякого управляющего события методы.

package innerclasses/controller;

public abstract class Event {

private long eventTime;

protected final long delayTime;

public Event(long delayTime) {

this.delayTime = delayTime; startO;

}

public void startO { // Позволяет перезапуск eventTime = System nanoTimeO + delayTime;

}

public boolean readyО {

return System.nanoTimeO >= eventTime;

}

public abstract void actionO; } ///:-

Конструктор просто запоминает время (от момента создания объекта), через которое должно выполняться событие Event, и после этого вызывает метод start(), который прибавляет к текущему времени интервал задержки, чтобы вы­числить время возникновения события. Метод start() отделен от конструктора, благодаря чему становится возможным «перезапуск» события после того, как его время уже истекло; таким образом, объект Event можно использовать много­кратно. Скажем, если вам понадобится повторяющееся событие, достаточно до­бавить вызов start() в метод action().

Метод ready() сообщает, что пора действовать — вызывать метод action(). Ко­нечно, метод ready() может быть переопределен любым производным классом, если событие Event активизируется не по времени, а по иному условию.

Следующий файл описывает саму систему управления, которая распоряжа­ется событиями и инициирует их. Объекты Event содержатся в контейнере List<Event>. На данный момент достаточно знать, что метод add() присоединяет объект Event к концу контейнера с типом List, метод size() возвращает количест­во элементов в контейнере, синтаксис foreach() осуществляет последовательную выборку элементов List, а метод remove() удаляет заданный элемент из контей­нера:

//: innerclasses/control 1er/Control1er.java // Обобщенная система управления package innerclasses.controller; import java.util.*;

public class Controller {

// Класс из пакета java.util для хранения событий Event: private List<Event> eventList = new ArrayList<Event>(); public void addEvent(Event c) { eventList.add(c): } public void run() {

while(eventList.size() > 0) {

for(Event e : new ArrayList<Event>(eventList)) if(e.readyO) {

System.out.println(e): e.actionO; eventList.remove(e):

}

}

} ///:-

Метод run() в цикле перебирает копию eventList в поисках событий Event, го­товых для выполнения. Для каждого найденного элемента он выводит инфор­мацию об объекте методом toString(), вызывает метод action(), а после этого уда­ляет событие из списка.

Заметьте, что в этой архитектуре совершенно неважно, что конкретно вы­полняет некое событие Event. В этом и состоит «изюминка» разработанной сис­темы; она отделяет постоянную составляющую от изменяющейся. «Вектором изменения» являются различные действия разнообразных событий Event, выра­жаемые посредством создания разных субклассов Event.

На этом этапе в дело вступают внутренние классы. Они позволяют добиться двух целей:

1.     Вся реализация системы управления создается в одном классе, с полной инкапсуляцией всей специфики данной реализации. Внутренние классы используются для представления различных разновидностей action(), не­обходимых для решения задачи.

2.     Внутренние классы помогают избежать громоздкой, неудобной реализа­ции, так как у них есть доступ к внешнему классу. Без этой возможности программный код очень быстро станет настолько неприятным, что вам захочется поискать другие альтернативы.

Рассмотрим конкретную реализацию системы управления, разработанную для управления функциями оранжереи[3]. Все события — включение света, воды и нагревателей, звонок и перезапуск системы — абсолютно разнородны. Однако система управления разработана так, что различия в коде легко изолируются. Внутренние классы помогают унаследовать несколько производных версий од­ного базового класса Event в пределах одного класса. Для каждого типа события от Event наследуется новый внутренний класс, и в его реализации action() запи­сывается управляющий код.

Как это обычно бывает при использовании каркасов приложений, класс GreenhouseControls наследует от класса Controller:

//: innerclasses/GreenhouseControls.java // Пример конкретного приложения на основе системы // управления, все находится в одном классе. Внутренние // классы дают возможность инкапсулировать различную // функциональность для каждого отдельного события, import innerclasses.control 1er.*,

public class GreenhouseControls extends Controller {

private boolean light = false,

public class LightOn extends Event {

public LightOndong delayTime) { super (delayTime). } public void actionO {

// Сюда помещается аппаратный вызов, выполняющий // физическое включение света, light = true;

}

public String toStringO { return "Свет включен"; }