Предупреждение: не стоит читать сей длинный скучный пост, если только вам не интересно, как же все-таки это сделать :)
Нередко API подсистемы позволяет выполнить произвольное действие в специальном контексте. Например вот так:
interface DataManager {
void doWithConstraintsDisabled(Runnable runnable);
}
Такой метод с одной стороны позволяет пользователю выполнять произвольные действия, а с другой системе контролировать, что до и, главное, после этих действий будут выполнены обязательные процедуры. Например, отключены и обратно включены все ограничения и проверки.
Типичное использование этого метода выглядит так:
final Datum pieceOfData = getNextDatum();
dataManager.doWithConstraintsDisabled(new Runnable() {
public void run() {
// это можно делать только с отключенными constraint'ами
makeComplexChanges(pieceOfData);
}
});
Таким образом, код клиента разбивается на две части: основную и внутри объекта, хотя благодаря свойствам языка Java, выглядит как будто программа непрерывна. Передать данные внутрь объекта очень просто благодаря анонимным классам и final-полям. А получить снаружи результат или, тем более, исключения - это уже задача.
Задача: Получить результат или исключение, возникшие в методе Runnable.run или Visitor.visitX аккуратно.
Вот сразу (теперь) хочу сказать, этот пост про то, как сделать аккуратно, а, увы, не коротко.
Получить результат несложно, надо только провести его через API. Делается это так:
interface Runnable2 {
RES run();
}
interface DataManager {
RES doWithConstraintsDisabled(Runnable2 runnable);
}
Теперь код внутри объкта Runnable2 может возвращать значение любого типа, а код снаружи его получить.
А вот с исключениями так просто не сделать. Но они иногда - важный элемент программы, и хочется, чтобы компилятор следил за ними.
Сделать это можно, но ценой удлиннения программы. Добавим в примере еще заголовок метода, чтобы видеть, что исключения пойдут наружу.
String processAndGetDescription(DataManager dataManager, final Datum pieceOfData) throws BadEx, PainEx {
abstract class Res {
abstract String get() throws BadEx, PainEx;
}
String description =
dataManager.doWithConstraintsDisabled(new Runnable2() {
public Res run() {
try {
// Вот это - полезный код
final String res = makeComplexChanges(pieceOfData);
return new Res() {
String get() {
return res;
}
};
} catch (final BadEx e) {
return new Res() {
String get() throws BadEx {
throw e;
}
};
} catch (final PainEx e) {
return new Res() {
String get() throws PainEx {
throw e;
}
};
}
}
}).get();
return "Description: "+description;
}
Внутри блока try выполняется требуемая операция. Но исключения, которые она вызывает не могут покинуть метод Runnable2.run, потому что они не объявлены для него. (Объявить их мы не можем, потому что интерфейс Runnable2 не должен зависеть от кода, который его использует.)
Провести эти исключения можно через специально сделанный абстрактный класс Res. Его метод get и служит объявлением компилятору, какие исключения здесь допустимы. Когда снаружи мы вызовем этот метод, мы либо получим результат, либо исключение.
Результат: ценой нескольких строк на каждый тип данных/исключений мы получили возможнсть провести из Runnable/Visitor наружу исключения и возвращаемое значение, сохранив полностью контроль компилятора за типами данных и исключений.