Ich habe einige Schnittstellen, die von Dritten in Zukunft implementiert werden sollen, und ich stelle selbst eine Basisimplementierung bereit. Ich werde nur ein paar verwenden, um das Beispiel zu zeigen.
Derzeit sind sie definiert als
Artikel:
public interface Item {
String getId();
String getName();
}
ItemStack:
public interface ItemStackFactory {
ItemStack createItemStack(Item item, int quantity);
}
ItemStackContainer:
public interface ItemStackContainer {
default void add(ItemStack stack) {
add(stack, 1);
}
void add(ItemStack stack, int quantity);
}
Jetzt, Item
und ItemStackFactory
ich kann absolut voraussehen, dass Dritte es in Zukunft erweitern müssen. ItemStackContainer
könnte auch in Zukunft erweitert werden, jedoch nicht auf eine Weise, die ich außerhalb meiner bereitgestellten Standardimplementierung vorhersehen kann.
Jetzt versuche ich, diese Bibliothek so robust wie möglich zu gestalten. Dies befindet sich noch im Anfangsstadium (Pre-Pre-Alpha), daher kann dies ein Akt des Over Engineering (YAGNI) sein. Ist dies ein geeigneter Ort, um Generika zu verwenden?
public interface ItemStack<T extends Item> {
T getItem();
int getQuantity();
}
Und
public interface ItemStackFactory<T extends ItemStack<I extends Item>> {
T createItemStack(I item, int quantity);
}
Ich befürchte, dass dies dazu führen könnte, dass Implementierungen und Verwendung schwieriger zu lesen und zu verstehen sind. Ich denke, es ist die Empfehlung, Generika nach Möglichkeit nicht zu verschachteln.
Antworten:
Sie verwenden Generika in Ihrer Benutzeroberfläche, wenn Ihre Implementierung wahrscheinlich auch generisch ist.
Beispielsweise ist jede Datenstruktur, die beliebige Objekte akzeptieren kann, ein guter Kandidat für eine generische Schnittstelle. Beispiele:
List<T>
undDictionary<K,V>
.Jede Situation, in der Sie die Typensicherheit allgemein verbessern möchten, ist ein guter Kandidat für Generika. A
List<String>
ist eine Liste, die nur mit Zeichenfolgen arbeitet.Jede Situation, in der Sie SRP allgemein anwenden und typspezifische Methoden vermeiden möchten, ist ein guter Kandidat für Generika. Beispielsweise können ein PersonDocument, ein PlaceDocument und ein ThingDocument durch ein ersetzt werden
Document<T>
.Das Abstract Factory-Muster ist ein guter Anwendungsfall für ein Generikum, während eine gewöhnliche Factory-Methode einfach Objekte erstellt, die von einer gemeinsamen, konkreten Schnittstelle erben.
quelle
class StackFactory { Stack<T> Create<T>(T item, int count); }
. Ich kann ein Beispiel dafür schreiben, was ich unter "Verfeinern eines Typparameters in einer Unterklasse" in einer Antwort verstanden habe, wenn Sie mehr Klarheit wünschen, obwohl die Antwort nur tangential mit der ursprünglichen Frage zusammenhängt.Ihr Plan, wie Sie Ihre Benutzeroberfläche allgemeiner gestalten können, scheint mir richtig zu sein. Ihre Frage, ob dies eine gute Idee wäre oder nicht, erfordert jedoch eine etwas kompliziertere Antwort.
Im Laufe der Jahre habe ich eine Reihe von Kandidaten für Software Engineering-Positionen für Unternehmen interviewt, für die ich gearbeitet habe, und mir ist klar geworden, dass die Mehrheit der Arbeitssuchenden mit der Verwendung allgemeiner Klassen vertraut ist (z. B. die Standard-Auflistungsklassen,) aber nicht beim Schreiben generischer Klassen. Die meisten haben in ihrer Karriere noch nie eine einzige generische Klasse geschrieben und sehen aus, als wären sie völlig verloren, wenn sie gebeten würden, eine zu schreiben. Wenn Sie also jemandem die Verwendung von Generika aufzwingen, der Ihre Schnittstelle implementieren oder Ihre Basisimplementierungen erweitern möchte, können Sie die Leute abschrecken.
Sie können jedoch versuchen, sowohl generische als auch nicht generische Versionen Ihrer Schnittstellen und Ihrer Basisimplementierungen bereitzustellen, damit Menschen, die keine Generika verwenden, glücklich in ihrer engen, typlastigen Welt leben können, während Menschen, die Generika können die Vorteile ihres breiteren Sprachverständnisses nutzen.
quelle
Da ist deine Entscheidung für dich getroffen. Wenn Sie keine Generika bereitstellen, müssen diese bei
Item
jeder Verwendung auf die Implementierung aktualisiert werden:Sie sollten zumindest
ItemStack
generisch machen, wie Sie es vorschlagen. Der tiefste Vorteil von Generika ist, dass Sie fast nie etwas umsetzen müssen, und das Anbieten von Code, der Benutzer zum Umsetzen zwingt, ist meiner Meinung nach eine Straftat.quelle
Wow ... ich sehe das ganz anders.
Das ist ein Fehler. Sie sollten keinen Code "für die Zukunft" schreiben.
Außerdem werden Schnittstellen von oben nach unten geschrieben. Sie sehen sich keine Klasse an und sagen: "Hey, diese Klasse könnte eine Schnittstelle vollständig implementieren, und dann kann ich diese Schnittstelle auch woanders verwenden." Das ist ein falscher Ansatz, da nur die Klasse, die eine Schnittstelle verwendet, wirklich weiß, wie die Schnittstelle aussehen soll. Stattdessen sollten Sie denken: "An dieser Stelle benötigt meine Klasse eine Abhängigkeit. Lassen Sie mich eine Schnittstelle für diese Abhängigkeit definieren. Wenn ich mit dem Schreiben dieser Klasse fertig bin, schreibe ich eine Implementierung dieser Abhängigkeit." Ob jemand Ihre Benutzeroberfläche jemals wiederverwenden und Ihre Standardimplementierung durch eine eigene ersetzen wird, ist völlig irrelevant. Sie können nie tun. Dies bedeutet nicht, dass die Schnittstelle unbrauchbar war. Dadurch folgt Ihr Code dem Inversion of Control-Prinzip, wodurch Ihr Code an vielen Stellen sehr erweiterbar ist. "
quelle
Ich denke, dass die Verwendung von Generika in Ihrer Situation zu komplex ist.
Wenn Sie eine generische Version von Schnittstellen auswählen, zeigt das Design dies an
Diese impliziten Annahmen und Einschränkungen sind sehr wichtige Dinge, die sorgfältig entschieden werden müssen. Daher sollten diese vorzeitigen Entwürfe insbesondere im Frühstadium nicht eingeführt werden. Einfaches, leicht verständliches, gemeinsames Design ist erwünscht.
quelle
Ich würde sagen, es hängt hauptsächlich von dieser Frage ab: Wenn jemand die Funktionalität von
Item
und erweitern mussItemStackFactory
,Im ersten Fall sind keine Generika erforderlich, im zweiten Fall wahrscheinlich.
quelle
Möglicherweise können Sie eine Schnittstellenhierarchie haben, in der Foo eine Schnittstelle und Bar eine andere ist. Wann immer ein Typ beides sein muss, ist es eine FooAndBar.
Um ein Überschneiden zu vermeiden, erstellen Sie den FooAndBar-Typ nur, wenn dies erforderlich ist, und führen Sie eine Liste der Schnittstellen, die niemals etwas erweitern.
Dies ist für die meisten Programmierer viel komfortabler (die meisten mögen ihre Typprüfung oder möchten sich niemals mit statischer Typisierung jeglicher Art befassen). Sehr wenige Leute haben ihre eigene Generika-Klasse geschrieben.
Ebenfalls als Hinweis: Bei Schnittstellen geht es darum, welche möglichen Aktionen ausgeführt werden können. Sie sollten eigentlich Verben sein, keine Substantive.
quelle