Wie heißt das folgende (Anti) Muster? Was sind ihre Vor- und Nachteile?

28

In den letzten Monaten bin ich ein paar Mal über die folgende Technik / das folgende Muster gestolpert. Ich kann jedoch scheinbar keinen bestimmten Namen finden und bin mir auch nicht hundertprozentig sicher, welche Vor- und Nachteile er hat.

Das Muster lautet wie folgt:

Innerhalb einer Java-Schnittstelle wird wie gewohnt eine Reihe gängiger Methoden definiert. Bei Verwendung einer inneren Klasse geht jedoch eine Standardinstanz über die Schnittstelle verloren.

public interface Vehicle {
    public void accelerate();
    public void decelerate();

    public static class Default {
         public static Vehicle getInstance() {
             return new Car(); // or use Spring to retrieve an instance
         }
    }
 }

Für mich liegt der größte Vorteil darin, dass ein Entwickler nur über die Oberfläche und nicht über deren Implementierungen Bescheid wissen muss, z. B. für den Fall, dass er schnell eine Instanz erstellen möchte.

 Vehicle someVehicle = Vehicle.Default.getInstance();
 someVehicle.accelerate();

Außerdem habe ich gesehen, dass diese Technik zusammen mit Spring verwendet wird, um Instanzen abhängig von der Konfiguration dynamisch bereitzustellen. In dieser Hinsicht kann dies auch bei der Modularisierung hilfreich sein.

Trotzdem kann ich das Gefühl nicht loswerden, dass dies ein Missbrauch der Schnittstelle ist, da sie die Schnittstelle mit einer ihrer Implementierungen koppelt. (Prinzip der Abhängigkeitsinversion usw.) Kann mir jemand erklären, wie diese Technik heißt und welche Vor- und Nachteile sie hat?

Aktualisieren:

Nach einiger Zeit zum Nachdenken überprüfte ich erneut und stellte fest, dass die folgende Singleton-Version des Musters weitaus häufiger verwendet wurde. In dieser Version wird eine öffentliche statische Instanz über die Schnittstelle verfügbar gemacht, die nur einmal initialisiert wird (da das Feld endgültig ist). Außerdem wird die Instanz fast immer mit Spring oder einer generischen Factory abgerufen, die die Schnittstelle von der Implementierung entkoppelt.

public interface Vehicle {
      public void accelerate();
      public void decelerate();

      public static class Default {
           public static final Vehicle INSTANCE = getInstance();

           private static Vehicle getInstance() {
                return new Car(); // or use Spring/factory here
           }
      }
 }

 // Which allows to retrieve a singleton instance using...
 Vehicle someVehicle = Vehicle.Default.INSTANCE;

Kurz gesagt: Es scheint, dass dies ein benutzerdefiniertes Singleton- / Factory-Muster ist, mit dem im Grunde genommen eine Instanz oder ein Singleton über ihre Schnittstelle verfügbar gemacht werden kann. In Bezug auf die Nachteile wurden einige in den nachstehenden Antworten und Kommentaren genannt. Bisher scheint der Vorteil in seiner Bequemlichkeit zu liegen.

Jérôme
quelle
eine Art Singleton-Muster?
Bryan Chen
Ich denke, Sie haben Recht, dass die Schnittstelle nicht über seine Implementierungen "wissen" sollte. Sollte wahrscheinlich Vehicle.Defaultals Factory-Klasse in den Package-Namespace hochgeschoben werden, z VehicleFactory.
August
7
ÜbrigensVehicle.Default.getInstance() != Vehicle.Default.getInstance()
Konrad Morawski
20
Das Antimuster soll hier das Auto-Analogie-Antimuster verwenden, das eng mit dem Tier-Analogie-Antimuster verwandt ist. Die meiste Software hat keine Räder oder Beine.
Pieter B
@PieterB: :-)))) Du hast meinen Tag gemacht!
Doc Brown

Antworten:

24

Default.getInstanceIMHO ist nur eine sehr spezifische Form einer Factory-Methode , die mit einer Namenskonvention aus dem Singleton-Muster verwechselt wird (ohne jedoch eine Implementierung des letzteren zu sein). In der jetzigen Form ist dies eine Verletzung des " Single-Responsibility-Prinzips ", da die Schnittstelle (die bereits zum Deklarieren einer Klassen-API dient) die zusätzliche Verantwortung für die Bereitstellung einer Standardinstanz übernimmt, die in einer separaten Instanz viel besser platziert wäre VehicleFactoryKlasse.

Das Hauptproblem, das durch dieses Konstrukt verursacht wird, ist, dass es eine zyklische Abhängigkeit zwischen Vehicleund induziert Car. In der aktuellen Form ist es beispielsweise nicht möglich, Vehiclein einer Bibliothek und Carin einer anderen zu platzieren. Die Verwendung einer separaten Factory-Klasse und die Platzierung der Default.getInstanceMethode dort würde das Problem lösen. Es kann auch eine gute Idee sein, dieser Methode einen anderen Namen zu geben, um Verwirrung in Bezug auf Singletons zu vermeiden. Das Ergebnis könnte also so etwas wie sein

VehicleFactory.createDefaultInstance()

Doc Brown
quelle
Danke für deine Antwort! Ich bin damit einverstanden, dass dieses spezielle Beispiel eine Verletzung des SRP darstellt. Ich bin mir jedoch nicht sicher, ob es immer noch ein Verstoß ist, wenn die Implementierung dynamisch abgerufen wird. ZB mit Spring (oder einem ähnlichen Framework), um eine bestimmte Instanz abzurufen, die beispielsweise durch eine Konfigurationsdatei definiert ist.
Jérôme
1
@ Jérôme: IMHO ist eine nicht verschachtelte Klasse mit dem Namen VehicleFactorykonventioneller als das verschachtelte Konstrukt Vehicle.Default, insbesondere mit der irreführenden Methode "getInstance". Obwohl es möglich ist, die zyklische Abhängigkeit mit Spring zu umgehen, würde ich persönlich die erste Variante aus Gründen des gesunden Menschenverstands vorziehen. Und dennoch haben Sie die Möglichkeit, das Werk auf eine andere Komponente zu verlagern und anschließend die Implementierung so zu ändern, dass sie ohne Feder usw. funktioniert.
Doc Brown,
Häufig besteht der Grund für die Verwendung einer Schnittstelle anstelle einer Klasse darin, dass Klassen mit anderen Zwecken von Code verwendet werden können, für den eine Schnittstellenimplementierung erforderlich ist. Wenn eine Standardimplementierungsklasse alle Anforderungen von Code erfüllen kann, der einfach etwas benötigt, das die Schnittstelle auf "normale" Weise implementiert, erscheint es für die Schnittstelle nützlich, ein Mittel zum Erstellen einer Instanz anzugeben.
Supercat
Wenn Car eine sehr allgemeine Standardimplementierung von Vehicle ist, ist dies keine Verletzung von SRP. Das Fahrzeug hat nur noch einen Grund, sich zu ändern, z. B. wenn Google eine autodrive()Methode hinzufügt . Ihr sehr generisches Auto muss sich dann ändern, um diese Methode zu implementieren, z. B. durch Auslösen einer NotImplementedException, aber dies liefert keinen zusätzlichen Grund für eine Änderung des Fahrzeugs.
user949300
@ user949300: Die SRP ist kein "mathematisch nachweisbares" Konzept. Und Software-Design-Entscheidungen sind nicht immer "schwarz-weiß". Der ursprüngliche Code ist natürlich nicht so schlecht, dass er nicht im Produktionscode verwendet werden könnte, und es kann Fälle geben, in denen die Bequemlichkeit die zyklische Abhängigkeit überwiegt, wie das OP in seinem "Update" selbst festgestellt hat. Lesen Sie meine Antwort daher bitte als "Überlegen Sie, ob die Verwendung einer separaten Factory-Klasse nicht besser für Sie ist". Beachten Sie auch, dass der Autor seine ursprüngliche Frage ein wenig geändert hat, nachdem ich meine Antwort gegeben habe.
Doc Brown
3

Das sieht für mich wie ein Null-Objektmuster aus .

Aber das hängt stark davon ab, wie die CarKlasse implementiert ist. Wenn es auf "neutrale" Weise implementiert wird, ist es definitiv ein Null-Objekt. Das heißt, wenn Sie alle Nullprüfungen auf der Schnittstelle entfernen und die Instanz dieser Klasse anstelle jedes Vorkommens von setzen können null, muss der Code weiterhin ordnungsgemäß funktionieren.

Auch wenn es sich um dieses Muster handelt, besteht kein Problem darin, eine enge Kopplung zwischen der Schnittstelle selbst und der Implementierung des Nullobjekts herzustellen. Denn Null-Objekt kann überall sein, wo diese Schnittstelle verwendet wird.

Euphorisch
quelle
2
Hallo und danke für die Antwort. Obwohl ich mir ziemlich sicher bin, dass dies in meinem Fall nicht zur Implementierung des Musters "Null Object" verwendet wurde, ist es eine interessante Erklärung.
Jérôme