40

40


Замыкание, предоставляемое внутренним классом, — хорошее решение, го­раздо более гибкое и безопасное, чем указатель. Рассмотрим пример:
//: innerclasses/CalIbacks.java
// Использование внутренних классов
// для реализации обратных вызовов
package innerclasses;
import static net.mindview.util.Print.*;
interface Incrementable { void incrementO,
}
// Простая реализация интерфейса: class Call eel implements Incrementable { private int i = 0. public void incrementO { i++;
print(i);

class Mylncrement {
public void increment О { System, out. pri ntlnC'flpy гая операция") }; public static void f(MyIncrement mi) { mi.incrementО; }
}
// Если класс должен вызывать метод increment О // по-другому, необходимо использовать внутренний класс: class Callee2 extends Mylncrement { private int i = 0, private void increment О { super.increment(): i++;
print(i):
}
private class Closure implements Incrementable { public void increment О {
// Указывается метод внешнего класса;
// в противном случае возникает бесконечная рекурсия.
Са11ее2.this.increment();
}
}
Incrementable getCallbackReferenceO { return new ClosureO;



class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) { callbackReference = cbh, }
void go() { callbackReference incrementO; }
}
public class Callbacks {
public static void main(String[] args) { Call eel cl = new CalleelO; Callee2 c2 = new Callee2(); Mylncrement.f(c2), Caller callerl = new Caller(cl); Caller caller2 = new Caller(c2.getCallbackReferenceO); callerl. goO; callerl.goO; caller2.go(); caller2.go();
}
} /* Output: Другая операция 1 1 2
Другая операция 2
Другая операция 3
*///:-

Этот пример также демонстрирует различия между реализацией интерфейса внешним или внутренним классом. Класс Calleel — наиболее очевидное реше­ние задачи с точки зрения программирования. Класс Callee2 наследует от класса Mylncrement, в котором уже есть метод increment(), выполняющий действие, ни­как не связанное с тем, что ожидает от него интерфейс Incrementable. Когда класс Mylncrement наследуется в Callee2, метод increment() нельзя переопреде­лить для использования в качестве метода интерфейса Incrementable, поэтому нам приходится предоставлять отдельную реализацию во внутреннем классе. Также отметьте, что создание внутреннего класса не затрагивает и не изменяет существующий интерфейс внешнего класса.
Все элементы, за исключением метода 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 "Свет включен"; }
}
public class LightOff extends Event {
public LightOffdong delayTime) { super(delayTime); } public void actionO {
// Сюда помещается аппаратный вызов, выполняющий // физическое выключение света light = false;
}
public String toStringO { return "Свет выключен", }
}
private boolean water = false;
public class WaterOn extends Event {
public WaterOn(long delayTime) { super(delayTime), } public void actionO {
// Здесь размещается код включения '// системы полива, water = true;
}
public String toStringO {
return "Полив включен";
}
}
public class WaterOff extends Event {
public WaterOffdong delayTime) { super(delayTime); } public void actionO {
// Здесь размещается код выключения // системы полива water = false;
}
public String toStringO {
return "Полив отключен";
}
}
private String thermostat = "День";
public class Thermostaticght extends Event {
public Thermostaticght(long delayTime) { super(delayTime);
}
public void actionO {
// Здесь размещается код управления оборудованием thermostat = "Ночь";

public String toStringO {
return "Термостат использует ночной режим";
}
}
public class ThermostatDay extends Event {
public ThermostatDay(long delayTime) { super(delayTime);
}
public void actionO {
// Здесь размещается код управления оборудованием thermostat = "День";
}
public String toStringO {
return "Термостат использует дневной режим";
}
}
// Пример метода actionO, вставляющего
// самого себя в список событий.
public class Bell extends Event {
public Bell(long delayTime) { super(delayTime); } public void actionO {
addEvent(new Bell(delayTime));
}
public String toStringO { return "Бам!"; }
}
public class Restart extends Event { private Event[] eventList;
public Restartdong delayTime. Event[] eventList) { super(delayTime); this.eventList = eventList; for(Event e : eventList) addEvent(e);
}
public void actionO {
for(Event e : eventList) {
e. start О; // Перезапуск каждый раз addEvent(e);
}
startO; // Возвращаем это событие Event addEvent(this);
}
public String toStringO {
return "Перезапуск системы";
}
}
public static class Terminate extends Event {
public Terminatedong delayTime) { super(delayTime); }
public void actionO { System.exit(0); }
public String toStringO { return "Отключение"; }
}
} ///;-
Заметьте, что поля light, thermostat и ring принадлежат внешнему классу GreenhouseControls, и все же внутренние классы имеют возможность обращаться к ним, не используя особой записи и не запрашивая особых разрешений. Боль­шинство методов action() требует управления оборудованием оранжереи, что, скорее всего, привлечет в программу сторонние низкоуровневые вызовы.

Report Page