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() требует управления оборудованием оранжереи, что, скорее всего, привлечет в программу сторонние низкоуровневые вызовы.