Эккель Брюс
Шрифт:
Замыкания и обратные вызовы
Замыканием (closure) называется вызываемый объект, который сохраняет информацию о контексте, он был создан. Из этого определения видно, что внутренний класс является объектно-ориентированным замыканием, поскольку он не только содержит информацию об объекте внешнего класса («место создания»), но к тому же располагает ссылкой на весь объект внешнего класса, с помощью которой он может манипулировать всеми членами этого объекта, в том числе и закрытыми (private).
При обсуждении того, стоит ли включать в Java некое подобие указателей, самым веским аргументом «за» была возможность обратных вызовов (callback). В механизме обратного вызова некоторому стороннему объекту передается информация, позволяющая ему затем обратиться с вызовом к объекту, который произвел изначальный вызов. Это очень мощная концепция программирования, к которой мы еще вернемся. С другой стороны, при реализации обратного вызова на основе указателей вся ответственность за его правильное использование возлагается на программиста. Как было показано ранее, язык Java ориентирован на безопасное программирование, поэтому указатели в него включены не были.
Замыкание, предоставляемое внутренним классом, — хорошее решение, гораздо более гибкое и безопасное, чем указатель. Рассмотрим пример:
//: 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, он в состоянии вызвать только метод incrementO, и других возможностей у него нет (в отличие от указателя, с которым программист может вытворять все, что угодно).
Класс Caller получает ссылку на Incrementable в своем конструкторе (хотя передача ссылки для обратного вызова может происходить в любое время), а после этого использует ссылку для «обратного вызова» объекта Callee.
Главным достоинством обратного вызова является его гибкость — вы можете динамически выбирать функции, выполняемые во время работы программы.
Внутренние классы и система управления
В качестве более реального пример использования внутренних классов мы рассмотрим то, что я буду называть здесь системой управления (control framework).