Ich benutze eine Drittanbieter-Bibliothek. Sie übergeben mir ein POJO , das nach unseren Absichten und Zwecken wahrscheinlich so implementiert wird:
public class OurData {
private String foo;
private String bar;
private String baz;
private String quux;
// A lot more than this
// IMPORTANT: NOTE THAT THIS IS A PACKAGE PRIVATE CONSTRUCTOR
OurData(/* I don't know what they do */) {
// some stuff
}
public String getFoo() {
return foo;
}
// etc.
}
Aus vielen Gründen, einschließlich, aber nicht beschränkt auf das Einkapseln ihrer API und das Ermöglichen von Komponententests, möchte ich ihre Daten verpacken. Aber ich möchte nicht, dass meine Kernklassen von ihren Daten abhängig sind (auch hier aus Testgründen)! Im Moment habe ich so etwas:
public class DataTypeOne implements DataInterface {
private String foo;
private int bar;
private double baz;
public DataTypeOne(String foo, int bar, double baz) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
}
}
public class DataTypeTwo implements DataInterface {
private String foo;
private int bar;
private double baz;
public DataTypeOne(String foo, int bar, double baz, String quux) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
this.quux = quux;
}
}
Und dann das:
public class ThirdPartyAdapter {
public static makeMyData(OurData data) {
if(data.getQuux() == null) {
return new DataTypeOne(
data.getFoo(),
Integer.parseInt(data.getBar()),
Double.parseDouble(data.getBaz()),
);
} else {
return new DataTypeTwo(
data.getFoo(),
Integer.parseInt(data.getBar()),
Double.parseDouble(data.getBaz()),
data.getQuux();
);
}
}
Diese Adapterklasse ist mit den wenigen anderen Klassen gekoppelt, die über die Drittanbieter-API Bescheid wissen MÜSSEN, wodurch ihre Verbreitung im Rest meines Systems eingeschränkt wird. Aber ... diese Lösung ist GROSS! In Clean Code, Seite 40:
Mehr als drei Argumente (polyadisch) bedürfen einer besonderen Begründung - und sollten dann sowieso nicht verwendet werden.
Dinge, über die ich nachgedacht habe:
- Erstellen eines Factory-Objekts anstelle einer statischen Hilfsmethode
- Löst nicht das Problem der Bajillion Argumente
- Erstellen einer Unterklasse von DataTypeOne und DataTypeTwo mit einem abhängigen Konstruktor
- Hat noch einen polyadengeschützten Konstruktor
- Erstellen Sie vollständig separate Implementierungen, die derselben Schnittstelle entsprechen
- Mehrere der oben genannten Ideen gleichzeitig
Wie soll mit dieser Situation umgegangen werden?
Beachten Sie, dass dies keine Antikorruptionssituation ist . Es ist nichts falsch mit ihrer API. Die Probleme sind:
- Ich möchte nicht, dass MEINE Datenstrukturen vorhanden sind
import com.third.party.library.SomeDataStructure;
- Ich kann ihre Datenstrukturen in meinen Testfällen nicht aufbauen
- Meine derzeitige Lösung führt zu sehr sehr hohen Argumentationszahlen. Ich möchte die Anzahl der Argumente niedrig halten, OHNE ihre Datenstrukturen weiterzugeben.
- Diese Frage lautet " Was ist eine Antikorruptionsschicht?". Meine Frage lautet: " Wie kann ich ein Muster oder ein beliebiges Muster verwenden, um dieses Szenario zu lösen?"
Ich frage auch nicht nach Code (ansonsten wäre diese Frage SO), sondern nur nach einer ausreichenden Antwort, damit ich den Code effektiv schreiben kann (was diese Frage nicht bietet).
quelle
The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification — and then shouldn’t be used anyway.
Antworten:
Die Strategie, die ich bei mehreren Initialisierungsparametern angewendet habe, besteht darin, einen Typ zu erstellen, der nur die Parameter für die Initialisierung enthält
Dann nimmt der Konstruktor für DataTypeTwo ein DataTypeTwoParameters-Objekt und DataTypeTwo wird erstellt über:
Dies gibt eine Menge Gelegenheit, um deutlich zu machen, welche Parameter in DataTypeTwo enthalten sind und was sie bedeuten. Sie können auch sinnvolle Standardwerte im DataTypeTwoParameters-Konstruktor angeben, sodass nur Werte festgelegt werden können, die in einer beliebigen Reihenfolge festgelegt werden müssen, die dem Benutzer der API gefällt.
quelle
Integer.parseInt
? In einem Setter oder außerhalb der Parameterklasse?p.bar = Integer.parseInt("4")
.DataTypeTwoParameters
inDataTypeTwo
.Sie haben hier zwei separate Probleme: das Umbrechen einer API und die geringe Anzahl von Argumenten.
Beim Packen einer API besteht die Idee darin, die Schnittstelle wie von Grund auf neu zu gestalten und dabei nur die Anforderungen zu kennen. Sie sagen, dass an der API nichts falsch ist, und dann im selben Atemzug eine Reihe von Dingen auflisten, die an der API falsch sind: Testbarkeit, Konstruierbarkeit, zu viele Parameter in einem Objekt usw. Schreiben Sie die API, die Sie sich gewünscht haben. Wenn dies mehrere Objekte anstelle des einen erfordert, tun Sie dies. Wenn es erforderlich ist, eine Ebene höher zu den Objekten zu springen, die das POJO erstellen , tun Sie dies.
Sobald Sie Ihre gewünschte API haben, ist die Parameteranzahl möglicherweise kein Problem mehr. Wenn ja, gibt es eine Reihe von gängigen Mustern zu berücksichtigen:
Beachten Sie, dass diese Erstellungsmuster häufig einen polyadischen Konstruktor aufrufen, den Sie als in Ordnung betrachten sollten, wenn er gekapselt ist. Das Problem mit polyadischen Konstruktoren ist, dass sie nicht nur einmal aufgerufen werden. Sie müssen sie jedes Mal aufrufen, wenn Sie ein Objekt erstellen müssen.
Beachten Sie, dass es in der Regel viel einfacher und leichter zu handhaben ist, die zugrunde liegende API zu erreichen, indem Sie einen Verweis auf das
OurData
Objekt speichern und die Methodenaufrufe weiterleiten, anstatt zu versuchen, die internen Funktionen erneut zu implementieren. Beispielsweise:quelle
OurData
Objekt" - dies ist das, was ich zumindest in der Basisklasse vermeiden möchte, um sicherzustellen, dass keine Abhängigkeit besteht.DataInterface
. Sie erstellen eine weitere Implementierung für Ihre Scheinobjekte.Ich glaube, Sie interpretieren die Empfehlung von Onkel Bob zu streng. Für normale Klassen, mit Logik und Methoden und Konstruktoren und dergleichen, fühlt sich ein polyadischer Konstruktor tatsächlich sehr nach Code-Geruch an. Aber für etwas, das ausschließlich ein Datencontainer ist, der Felder verfügbar macht und von einem Factory-Objekt generiert wird, denke ich nicht, dass es so schlimm ist.
Sie können das in einem Kommentar vorgeschlagene Parameter-Objekt-Muster verwenden, um diese Konstruktor-Parameter für Sie zu verpacken, wobei Ihr lokaler Datentyp-Wrapper bereits im Wesentlichen ein Parameter-Objekt ist. Alles, was Ihr Parameter-Objekt tun wird, ist, die Parameter zu packen (wie werden Sie sie erstellen? Mit einem polyadischen Konstruktor?) Und sie eine Sekunde später in ein fast identisches Objekt zu entpacken.
Wenn Sie keine Setter für Ihre Felder anzeigen und sie aufrufen möchten, ist es meiner Meinung nach in Ordnung, sich an einen polyadischen Konstruktor in einer gut definierten und gekapselten Factory zu halten.
quelle