русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Замыкания и обратные вызовы


Дата добавления: 2015-06-12; просмотров: 971; Нарушение авторских прав


Замыканием (closure) называется вызываемый объект, который сохраняет информацию о контексте, он был создан. Из этого определения видно, что внутренний класс является объектно-ориентированным замыканием, поскольку он не только содержит информацию об объекте внешнего класса («место создания»), но к тому же располагает ссылкой на весь объект внешнего класса, с помощью которой он может манипулировать всеми членами этого объекта, в том числе и закрытыми (private).

При обсуждении того, стоит ли включать в Java некое подобие указателей, самым веским аргументом «за» была возможность обратных вызовов (callback). В механизме обратного вызова некоторому стороннему объекту передается ин­формация, позволяющая ему затем обратиться с вызовом к объекту, который произвел изначальный вызов.

Это очень мощная концепция программирования, к которой мы еще вернемся. С другой стороны, при реализации обратного вызова на основе указателей вся ответственность за его правильное использование возлагается на программиста. Как было показано ранее, язык Javaориентирован на безопасное программирование, поэтому указатели в него включены не были. Замыкание, предоставляемое внутренним классом, — хорошее решение, гораздо более гибкое и безопасное, чем указатель. Рассмотрим пример:

//: innerclasses/Callbacks.java

// Использование внутренних классов

// для реализации обратных вызовов

package innerclasses;

import static net.mindview.util.Print.*;

 

interface Incrementable {

void increment();

}

 

// Простая реализация интерфейса:

class Callee1 implements Incrementable {

private int i = 0;

public void increment() {

i++;

print(i);

}

}

 

class MyIncrement {

public void increment() { print("Other operation"); }

static void f(MyIncrement mi) { mi.increment(); }



}

 

// Если класс должен вызывать метод increment()

// по-другому, необходимо использовать внутренний класс:

class Callee2 extends MyIncrement {

private int i = 0;

public void increment() {

super.increment();

i++;

print(i);

}

private class Closure implements Incrementable {

public void increment() {

// Указывается метод внешнего класса;

// в противном случае возникает бесконечная рекурсия.

Callee2.this.increment();

}

}

Incrementable getCallbackReference() {

return new Closure();

}

}

 

class Caller {

private Incrementable callbackReference;

Caller(Incrementable cbh) { callbackReference = cbh; }

void go() { callbackReference.increment(); }

}

 

public class Callbacks {

public static void main(String[] args) {

Callee1 c1 = new Callee1();

Callee2 c2 = new Callee2();

MyIncrement.f(c2);

Caller caller1 = new Caller(c1);

Caller caller2 = new Caller(c2.getCallbackReference());

caller1.go();

caller1.go();

caller2.go();

caller2.go();

}

}

<spoiler text="Output:">

Other operation

Other operation

Other operation

</spoiler> Этот пример также демонстрирует различия между реализацией интерфейса внешним или внутренним классом. Класс Callee1 — наиболее очевидное решение задачи с точки зрения программирования.

Класс Callee2 наследует от класса MyIncrement, в котором уже есть метод increment), выполняющий действие, никак не связанное с тем, что ожидает от него интерфейс Incrementable. Когда класс MyIncrement наследуется в Callee2, метод increment() нельзя переопределить для использования в качестве метода интерфейса Incrementable, поэтому нам приходится предоставлять отдельную реализацию во внутреннем классе. Также отметьте, что создание внутреннего класса не затрагивает и не изменяет существующий интерфейс внешнего класса.

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

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

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

 

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

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

Каркас приложения (application framework) — это класс или набор классов, разработанных для решения определенного круга задач. При работе с каркасами приложений обычно используется наследование от одного или нескольких классов, с переопределением некоторых методов. Код переопределенных методов адаптирует типовое решение, предоставляемое каркасом приложения, к вашим конкретным потребностям.

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

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

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

//: innerclasses/controller/Event.java

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

package innerclasses.controller;

 

public abstract class Event {

private long eventTime;

protected final long delayTime;

public Event(long delayTime) {

this.delayTime = delayTime;

start();

}

public void start() { // Позволяет перезапуск

eventTime = System.nanoTime() + delayTime;

}

public boolean ready() {

return System.nanoTime() >= eventTime;

}

public abstract void action();

}

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

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

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

//: innerclasses/controller/Controller.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)

// Make a copy so you're not modifying the list

// while you're selecting the elements in it:

for(Event e : new ArrayList<Event>(eventList))

if(e.ready()) {

System.out.println(e);

e.action();

eventList.remove(e);

}

}

}

Метод run() в цикле перебирает копию eventList в поисках событий Event, готовых для выполнения. Для каждого найденного элемента он выводит информацию об объекте методом toString(), вызывает метод action(), а после этого удаляет событие из списка. Заметьте, что в этой архитектуре совершенно неважно, что конкретно выполняет некое событие Event. В этом и состоит «изюминка» разработанной системы; она отделяет постоянную составляющую от изменяющейся. «Вектором изменения» являются различные действия разнообразных событий Event, выражаемые посредством создания разных субклассов Event. На этом этапе в дело вступают внутренние классы. Они позволяют добиться двух целей:

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

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

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

//: innerclasses/GreenhouseControls.java

// Пример конкретного приложения на основе системы

// управления, все находится в одном классе. Внутренние

// классы дают возможность инкапсулировать различную

// функциональность для каждого отдельного события

import innerclasses.controller.*;

 

public class GreenhouseControls extends Controller {

private boolean light = false;

public class LightOn extends Event {

public LightOn(long delayTime) { super(delayTime); }

public void action() {

// Сюда помещается аппаратный вызов

// физическое включение света

light = true;

}

public String toString() { return "Light is on"; }

}

public class LightOff extends Event {

public LightOff(long delayTime) { super(delayTime); }

public void action() {

// Сюда помещается аппаратный вызов

// физическое выключение света

light = false;

}

public String toString() { return "Light is off"; }

}

private boolean water = false;

public class WaterOn extends Event {

public WaterOn(long delayTime) { super(delayTime); }

public void action() {

// Сюда помещается аппаратный вызов.

// выключения системы полива

water = true;

}

public String toString() {

return "Greenhouse water is on";

}

}

public class WaterOff extends Event {

public WaterOff(long delayTime) { super(delayTime); }

public void action() {

// Сюда помещается аппаратный вызов.

// выключения системы полива

water = false;

}

public String toString() {

return "Greenhouse water is off";

}

}

private String thermostat = "Day";

public class ThermostatNight extends Event {

public ThermostatNight(long delayTime) {

super(delayTime);

}

public void action() {

// Сюда помещается аппаратный вызов.

// thermostat = "Ночь";

thermostat = "Night";

}

public String toString() {

return "Thermostat on night setting";

}

}

public class ThermostatDay extends Event {

public ThermostatDay(long delayTime) {

super(delayTime);

}

public void action() {

// Сюда помещается аппаратный вызов.

// thermostat = "День"

thermostat = "Day";

}

public String toString() {

return "Thermostat on day setting";

}

}

// Пример метода action(), вставляющего

// самого себя в список событий.

public class Bell extends Event {

public Bell(long delayTime) { super(delayTime); }

public void action() {

addEvent(new Bell(delayTime));

}

public String toString() { return "Bing!"; }

}

public class Restart extends Event {

private Event[] eventList;

public Restart(long delayTime, Event[] eventList) {

super(delayTime);

this.eventList = eventList;

for(Event e : eventList)

addEvent(e);

}

public void action() {

for(Event e : eventList) {

e.start(); // Перезапуск каждый раз

addEvent(e);

}

start(); // Возвращаем это событие Event

addEvent(this);

}

public String toString() {

return "Restarting system";

}

}

public static class Terminate extends Event {

public Terminate(long delayTime) { super(delayTime); }

public void action() { System.exit(0); }

public String toString() { return "Terminating"; }

}

}

Заметьте, что поля light, thermostat и ring принадлежат внешнему классу GreenhouseControls, и все же внутренние классы имеют возможность обращаться к ним, не используя особой записи и не запрашивая особых разрешений. Большинство методов action() требует управления оборудованием оранжереи, что, скорее всего, привлечет в программу сторонние низкоуровневые вызовы.

В основном классы Event похожи друг на друга, однако классы Bell и Restart представляют собой особые случаи. Bell выдает звуковой сигнал и добавляет себя в список событий, чтобы звонок позднее сработал снова. Заметьте, что внутренние классы действуют почти как множественное наследование: классы Bell и Restart имеют доступ ко всем методам класса Event, а также ко всем методам внешнего класса GreenhouseControls.

Классу Restart передается массив объектов Event, которые он добавляет в контроллер. Так как Restart также является объектом Event, вы можете добавить этот объект в список событий в методе Restart.action(), чтобы система регулярно перезапускалась.

Следующий класс настраивает систему, создавая объект GreenhouseControls и добавляя в него разнообразные типы объектов Event. Это пример шаблона проектирования «команда» — каждый объект в EventList представляет собой запрос, инкапсулированный в объекте:

//: innerclasses/GreenhouseController.java

// Configure and execute the greenhouse system.

// {Args: 5000}

import innerclasses.controller.*;

 

public class GreenhouseController {

public static void main(String[] args) {

GreenhouseControls gc = new GreenhouseControls();

// Вместо жесткого кодирования фиксированных данных

// можно было бы считать информацию для настройки

// из текстового файла:

gc.addEvent(gc.new Bell(900));

Event[] eventList = {

gc.new ThermostatNight(0),

gc.new LightOn(200),

gc.new LightOff(400),

gc.new WaterOn(600),

gc.new WaterOff(800),

gc.new ThermostatDay(1400)

};

gc.addEvent(gc.new Restart(2000, eventList));

if(args.length == 1)

gc.addEvent(

new GreenhouseControls.Terminate(

new Integer(args[0])));

gc.run();

}

}

<spoiler text="Output:">

Bing!

Thermostat on night setting

Light is on

Light is off

Greenhouse water is on

Greenhouse water is off

Thermostat on day setting

Restarting system

Terminating

</spoiler> Класс инициализирует систему, включая в нее нужные события. Если передать программе параметр командной строки, она завершается по истечении заданного количества миллисекунд (используется при тестировании). Конечно, чтобы программа стала более гибкой, описания событий следовало бы не включать в программный код, а загружать из файла. Этот пример поможет понять всю ценность механизма внутренних классов, особенно в случае с системами управления.



<== предыдущая лекция | следующая лекция ==>
Внутренние классы: зачем? | Наследование от внутренних классов


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.135 сек.