Legacy AutoClosable

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.

Schreibe einen Kommentar