"Neu" sein oder nicht sein ...

Hallo wieder. Im Vorgriff auf den Beginn der Grund- und Fortgeschrittenenkurse zur Android-Entwicklung haben wir für Sie eine weitere interessante Übersetzung vorbereitet.




Für die Abhängigkeitsinjektion müssen wir die neuen Operatoren und die Anwendungslogik trennen . Diese Trennung ermutigt Sie, Fabriken in Ihrem Code zu verwenden, die für die Verknüpfung Ihrer Anwendung verantwortlich sind . Anstatt Fabriken zu schreiben, würden wir jedoch lieber die automatische Abhängigkeitsinjektion wie GUICE verwenden, um die Bindung zu übernehmen. Aber kann uns die Abhängigkeitsinjektion wirklich vor allen neuen Operatoren retten?
Schauen wir uns zwei Extreme an. Angenommen, Sie haben eine MusicPlayer-Klasse, die AudioDevice erhalten soll. Hier möchten wir die Abhängigkeitsinjektion verwenden und ein AudioDevice im MusicPlayer-Konstruktor anfordern. Auf diese Weise können wir ein testfreundliches AudioDevice hinzufügen, mit dem wir behaupten können, dass der richtige Sound aus unserem MusicPlayer stammt. Wenn wir den neuen Operator verwenden würden, um eine Instanz von BuiltInSpeakerAudioDevice zu erstellen, hätten wir einige Testschwierigkeiten. Nennen wir also Objekte wie AudioDevice oder MusicPlayer "Injectable". Injizierbar sind die Objekte, die Sie in den Konstruktoren anfordern und von denen das Framework zum Implementieren von Abhängigkeiten erwartet, dass Sie diese bereitstellen.

Nun zum anderen Extrem. Angenommen, Sie haben ein int int-Grundelement, möchten es jedoch automatisch in eine Ganzzahl packen. Am einfachsten ist es, eine neue Ganzzahl (5) aufzurufen, und das ist das Ende. Aber wenn die Abhängigkeitsinjektion das neue „Neue“ ist, warum nennen wir dann New Inline? Wird es unseren Tests schaden? Es stellt sich heraus, dass Frameworks zum Einfügen von Abhängigkeiten Ihnen nicht die von Ihnen benötigte Ganzzahl geben können, da sie nicht verstehen, um welche Art von Ganzzahl es sich handelt. Dies ist ein kleines Spielzeugbeispiel. Schauen wir uns also etwas Komplexeres an.

Angenommen, ein Benutzer hat eine E-Mail-Adresse in das Anmeldefeld eingegeben und Sie müssen eine neue E-Mail-Adresse anrufen («a@b.com») Können wir es so lassen oder sollten wir E-Mail in unserem Konstruktor anfordern? Auch hier kann das Abhängigkeitsinjektionsframework keine E-Mail bereitstellen, da Sie zuerst den String abrufen müssen, in dem sich die E-Mail befindet. Und es gibt viele Strings zur Auswahl. Wie Sie sehen, gibt es viele Objekte, die das Abhängigkeitsinjektionsframework niemals bereitstellen kann. Nennen wir sie "Newable", da Sie gezwungen sind, manuell für sie new aufzurufen .

Lassen Sie uns zunächst einige Grundregeln festlegen. Eine Injectable-Klasse kann andere Injectable in ihrem Konstruktor abfragen. (Manchmal rufe ich Injectable als Serviceobjekt auf, aber dieser Begriff ist überladen.) Injectable hat in der Regel Schnittstellen, da die Möglichkeit besteht, dass wir sie durch eine zum Testen geeignete Implementierung ersetzen müssen. Injectable kann jedoch niemals Nicht-Injectable (Newable) in seinem Konstruktor abfragen. Dies liegt daran, dass das Abhängigkeitsinjektionsframework nicht weiß, wie ein Newable erstellt wird. Hier sind einige Beispiele für Klassen, die ich von meinem Abhängigkeitsinjektionsframework erwarten würde: CreditCardProcessor, MusicPlayer, MailSender, OfflineQueue. Ebenso kann Newable von anderen Newable in ihrem Konstruktor angefordert werden, aber nicht Injectable (manchmal nenne ich Newable als Wertobjekt, aber wieder,dieser Begriff ist überladen). Einige Beispiele für Newable: E-Mail, MailMessage, Benutzer, Kreditkarte, Song. Wenn Sie diesen Unterscheidungen folgen, ist Ihr Code einfach zu testen und zu bearbeiten. Wenn Sie gegen diese Regeln verstoßen, ist Ihr Code schwer zu testen.

Schauen wir uns das Beispiel von MusicPlayer und Song an

class Song {
  Song(String name, byte[] content);
}
class MusicPlayer {
  @Injectable
  MusicPlayer(AudioDevice device);
  play(Song song);
}

Beachten Sie, dass Song nur Objekte abfragt, die Newable sind. Dies macht es sehr einfach, einen Song in einem Test zu instanziieren. MusicPlayer ist wie sein AudioDevice-Argument vollständig injizierbar, sodass es aus dem Framework für die Abhängigkeitsinjektion abgerufen werden kann.

Nun wollen wir sehen, was passiert, wenn MusicPlayer gegen die Regel verstößt und Newable in seinem Konstruktor anfordert.

class Song {
  String name;
  byte[] content;
  Song(String name, byte[] content);
}
class MusicPlayer {
  AudioDevice device;
  Song song;
  @Injectable
  MusicPlayer(AudioDevice device, Song song);
  play();
}

Hier ist Song immer noch neu und es ist einfach, es in Ihrem Test oder in Ihrem Code zu erstellen. MusicPlayer ist bereits ein Problem. Wenn Sie MusicPlayer von Ihrem Framework nach der Abhängigkeitsinjektion fragen, stürzt es ab, da das Framework nicht weiß, um welchen Song es sich handelt. Die meisten Leute, die neu in Abhängigkeitsinjektions-Frameworks sind, machen diesen Fehler selten, da es leicht zu bemerken ist: Ihr Code wird nicht funktionieren.

Nun wollen wir sehen, was passiert, wenn Song gegen die Regel verstößt und Injectable in seinem Konstruktor anfordert.

class MusicPlayer {
  AudioDevice device;
  @Injectable
  MusicPlayer(AudioDevice device);
}
class Song {
  String name;
  byte[] content;
  MusicPlayer palyer;
  Song(String name, byte[] content, MusicPlayer player);
  play();
}
class SongReader {
  MusicPlayer player
  @Injectable
  SongReader(MusicPlayer player) {
    this.player = player;
  }
  Song read(File file) {
    return new Song(file.getName(),
                    readBytes(file),
                    player);
  }
}

Auf den ersten Blick ist alles in Ordnung. Aber denken Sie darüber nach, wie Songs erstellt werden. Vermutlich sind die Songs auf der Festplatte gespeichert, daher benötigen wir SongReader. SongReader muss MusicPlayer anfordern, damit beim Aufrufen von new for Song die Abhängigkeiten von Song von MusicPlayer erfüllt werden können. Haben Sie hier etwas falsches bemerkt? Welchen Schrecken muss SongReader über MusicPlayer wissen? Dies ist ein Verstoß gegen das Gesetz von Demeter. SongReader sollte nichts über MusicPlayer wissen. Zumindest, weil SongReader keine Methoden mit MusicPlayer aufruft. Er kennt MusicPlayer nur, weil Song die Trennung von Newable / Injectable verletzt hat. SongReader bezahlt den Fehler in Song. Da der Ort, an dem der Fehler gemacht wird und an dem sich die Konsequenzen manifestieren, nicht derselbe ist, ist dieser Fehler sehr subtil und schwer zu diagnostizieren. Es bedeutet auch, dass viele Menschen diesen Fehler wahrscheinlich machen.

In Bezug auf das Testen ist dies ein echter Schmerz. Angenommen, Sie haben SongWriter und möchten sicherstellen, dass Song korrekt auf die Festplatte serialisiert wird. Sie müssen einen MockMusicPlayer erstellen, damit Sie ihn an Song übergeben können, damit Sie ihn an SongWritter übergeben können. Warum stoßen wir hier überhaupt auf MusicPlayer? Schauen wir es uns anders an. Song ist das, was Sie möglicherweise serialisieren möchten, und der einfachste Weg, dies zu tun, ist die Verwendung der Java-Serialisierung. Daher serialisieren wir nicht nur Song, sondern auch MusicPlayer und AudioDevice. Weder MusicPlayer noch AudioDevice sollten serialisiert werden. Wie Sie sehen können, erleichtern kleine Änderungen die Testbarkeit erheblich.

Wie Sie sehen, ist die Arbeit mit Code einfacher, wenn wir diese beiden Arten von Objekten trennen. Wenn Sie sie mischen, ist Ihr Code schwer zu testen. Neu sind die Objekte, die sich am Ende des Objektdiagramms Ihrer Anwendung befinden. Newable kann von anderen Newable abhängen, z. B. wie CreditCard von der Adresse abhängt, die von der Stadt abhängen kann - dies sind Blätter des Anwendungsdiagramms. Da es sich um Blätter handelt und sie nicht mit externen Diensten kommunizieren (externe Dienste sind injizierbar), müssen sie keine Stubs ausführen. Nichts sieht mehr nach String aus als String selbst. Warum sollte ich einen Stub für den Benutzer erstellen, wenn ich nur einen neuen Benutzer anrufen kann, warum sollte ich einen Stub für Folgendes erstellen: E-Mail, MailMessage, Benutzer, Kreditkarte, Song? Rufen Sie einfach neu an und beenden Sie es.

Lassen Sie uns nun auf etwas sehr Feines achten. Es ist normal, dass Newable etwas über Injectable weiß. Was nicht normal ist, ist, dass Newable einen Verweis auf Injectable als Feld hat. Mit anderen Worten, Song kann etwas über MusicPlayer wissen. Beispielsweise ist es normal, dass Injectable MusicPlayer über den Stapel an Newable Song übergeben wird. Weil das Durchlaufen des Stapels unabhängig vom Framework für die Abhängigkeitsinjektion ist. Wie in diesem Beispiel:

class Song {
  Song(String name, byte[] content);
  boolean isPlayable(MusicPlayer player);
}

Das Problem tritt auf, wenn Song ein Linkfeld zu MusicPlayer hat. Verknüpfungsfelder werden über den Konstruktor festgelegt, was zu einer Verletzung des Demeter-Gesetzes für den Anrufer und zu Schwierigkeiten bei unseren Tests führt.

Erfahren Sie mehr über Kurse



All Articles