„Die Neugier steht immer an erster Stelle eines Problems, das gelöst werden will.“
Galileo Galilei
Nichts ist ärgerlicher bei der Verbesserung von Legacy Software als die Abhängigkeit von Klassen, die sich modernen Sprach Konstrukten verweigern. Eine besondere Gruppe dabei sind all die Klassen, die sich zieren vom Interface AutoClosable
zu erben. Diese Klassen sind durch veraltete Abhängigkeiten in das eigene Projekt geflossen und diese Abhängigkeiten sind aus mannigfaltigen Gründen dem Zugriff des Entwicklers entzogen.
LegacyPersistenceAbstraction abstraction = null; try { abstraction = configuration.connect(); DomainObject instance = abstraction.loadById(12345); if (instance != null) { instance.activate(); } } catch (PersistenceException e) { log.warn(e.getMessage(), e); } finally { if (abstraction != null) { try { abstraction.dispose(); } catch (PersistenceException ignored) { } } }
Der hier dargestellte Quellcode zeigt eine groteske Variante einer Persistenz API. Die Klasse LegacyPersistenceAbstraction
implementiert nicht das Interface AutoClosable
und niemand ist bereit an der API weitere Änderungen vorzunehmen. Dem Entwickler bleiben die Möglichkeiten schweren Herzens den alten Quellcode zu behalten, alles neu zu schreiben oder mit einem Trick die Verwendung von AutoClosable
zu ermöglichen.
Der Trick ist die Klasse AutoClosables
, die beliebige Instanzen anderer Klassen mit dem AutoClosable
Möglichkeiten versorgen kann.
import java.util.Objects; public class AutoClosables<W, E extends Exception> implements AutoCloseable { private final W wrapped; private final ConsumerWithException<W, E> closer; public interface ConsumerWithException<T, E extends Exception> { void accept(T var1) throws E; } public static <T, E extends Exception> AutoClosables<T, E> with(T wrapped, ConsumerWithException<T, E> closer) { return new AutoClosables<>(Objects.requireNonNull(wrapped), Objects.requireNonNull(closer)); } private AutoClosables(W wrapped, ConsumerWithException<W, E> closer) { this.wrapped = wrapped; this.closer = closer; } public W get() { return wrapped; } @Override public void close() throws E { closer.accept(wrapped); } }
Die Klasse AutoClosables
bekommt eine Instanz einer anderen Klasse und deren Close-Methode als ConsumerWithException
übergeben. Als AutoClosable
Implementierung ruft der AutoClosables
Wrapper dann die Close-Methode der anderen Klasse auf. Das obige Beispiel verändert sich damit wie folgt.
try (AutoClosableWrapper<LegacyPersistenceAbstraction , PersistenceException > closable = AutoClosableWrapper.with( configuration.connect(), LegacyPersistenceAbstraction::dispose)) { DomainObject instance = closable.get().loadById(12345); if (instance != null) { instance.activate(); } } catch (PersistenceException e) { log.warn(e.getMessage(), e); }
Der Code wird nicht nur kürzer und übersichtlicher, auch vergessene dispose
Aufrufe und ihre Probleme gehören der Vergangenheit an.