zur Übersicht
21. Mai 2016

Private Variablen erreichen mit XRayInterface

In diesem Beitrag möchte ich eine Möglichkeit eröffnen wie man auf private Variablen und Methoden zugreifen kann, indem man seine Klassen an passende Interfaces adaptiert. Warum? In meinem früheren Beitrag über das Reparieren von Legacy Code habe ich kurz erwähnt, dass eine große Herausforderung beim Testen der Zugriff auf den verborgenem Zustand (private Variablen/Felder) vor und nach der Ausführung der Methoden ist.


Zugriff auf private Variablen/Methoden ist z.B. in folgenden Situationen hilfreich:

  • wenn ich explizit die Struktur eines Objekts visualisieren möchte
  • wenn ich Teile des Zustands eines Objekts mit Mock-Objekten ersetzen möchte
  • wenn das Interface keinen Zugriff auf eine Information ermöglicht, die entscheidend für den weiteren Algorithmus ist
  • wenn ich Methoden zwar nicht öffentlich zugänglich machen möchte, aber dennoch deren Randbedingungen testen möchte

Sicher hat der eine oder andere schon gemutmaßt, dass man den versteckten Zustand über Java Reflection erreichen kann. Aber es gibt auch eine eleganteren Ansatz.

Jeder sichtbare Zustand ist erreichbar über ein Interface. Nehmen wir als Beispiel eine ganz normale Java-Bean Name:

public class Name {

    private String firstName;
    private String lastName;
    
    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }
    
    public String getLastName() {
        return lastName;
    }
}

Der innere Zustand ist nicht schreibbar, aber lesbar. Das implizite Interface dieser Klasse ist:

interface NameReadable {
    String getFirstName();
    String getLastName();
}

Java erlaubt zwar nicht Name nach NameReadable zu casten, aber Name könnte ohne weiteres NameReadable implementieren. Nehmen wir jetzt an, dass die Klasse Name nicht so passend geschrieben wäre:

public class Name {

    private String firstName;
    private String lastName;

    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }
}

Das Attribut lastName ist nicht lesbar. Wäre Name vom Interface NameReadable oder zumindest in dieses Interface konvertierbar, so könnten wir das Attribut lesen. Aber wie könnten wir das bewerkstelligen? Die naive Art und Weise, nämlich casten, funktioniert leider nicht:

NameReadable read = (NameReadable) new Name("Foo","Bar"); //ClassCastException
System.out.println(read.getLastName());

Aber der Ansatz ist der richtige – mit XRayInterface erstellt man einen Adapter für den Zieltyp, der das gewünschte Interface automatisch implementiert:

NameReadable read = XRayInterface.xray(new Name("Foo", "Bar"))
    .to(NameReadable.class);
System.out.println(read.getLastName()); // => Bar

Das ist nur wenig mehr Text als casten (noch weniger, wenn man XRayInterface statisch importiert).

Analog zum Lese-Zugriff könnte ich jetzt auch eleganten Schreibzugriff erreichen:

interface NameWritable {
    void setFirstName(String firstName);
    void setLastName(String lastName);
}

NameWritable write = XRayInterface.xray(new Name("Foo", "Bar"))
    .to(NameWritable.class);
write.setLastName("FooBar");

Generell ist die XRayInterface-Technik so konzipiert, dass jede Interface-Methode sinnvoll auf die Objektmethoden/variablen abgebildet wird.

XRayInterface bietet dazu noch ganz andere Möglichkeiten.

  • Zugriff auf (private) Methoden (analog zu obiger Methodik)
  • Zugriff auf (private) Konstruktoren und statische Methoden/Variablen (analog zu obiger Methodik)
  • Zugriff auf (private) Variablen – an Settern/Gettern vorbei (mit Annotationen kann man dem Interface mitteilen ob die Abbildung präferiert auf Methoden oder auf Instanzvariablen durchgeführt werden soll.