Angenommen, Sie haben zwei Schnittstellen:
interface Readable {
public void read();
}
interface Writable {
public void write();
}
In einigen Fällen können die implementierenden Objekte nur eines dieser Objekte unterstützen, in vielen Fällen unterstützen die Implementierungen jedoch beide Schnittstellen. Die Leute, die die Schnittstellen benutzen, müssen etwas tun wie:
// can't write to it without explicit casting
Readable myObject = new MyObject();
// can't read from it without explicit casting
Writable myObject = new MyObject();
// tight coupling to actual implementation
MyObject myObject = new MyObject();
Keine dieser Optionen ist besonders praktisch, insbesondere wenn Sie dies als Methodenparameter verwenden möchten.
Eine Lösung wäre, eine Wrapping-Schnittstelle zu deklarieren:
interface TheWholeShabam extends Readable, Writable {}
Dies hat jedoch ein spezifisches Problem: Alle Implementierungen, die Readable und Writable unterstützen, müssen TheWholeShabam implementieren, wenn sie mit Benutzern der Schnittstelle kompatibel sein sollen. Auch wenn es nichts anderes bietet als das garantierte Vorhandensein beider Schnittstellen.
Gibt es eine saubere Lösung für dieses Problem oder sollte ich mich für die Wrapper-Schnittstelle entscheiden?
AKTUALISIEREN
Es ist in der Tat oftmals erforderlich, ein Objekt zu haben, das sowohl lesbar als auch beschreibbar ist, so dass eine einfache Trennung der Bedenken in den Argumenten nicht immer eine saubere Lösung ist.
UPDATE2
(Als Antwort extrahiert, damit es einfacher ist, Kommentare abzugeben.)
UPDATE3
Bitte beachten Sie, dass der primäre Verwendungszweck hierfür nicht Streams sind (obwohl diese ebenfalls unterstützt werden müssen). Streams unterscheiden sehr genau zwischen Input und Output und es gibt eine klare Trennung der Verantwortlichkeiten. Stellen Sie sich stattdessen so etwas wie einen Bytepuffer vor, in dem Sie ein Objekt benötigen, in das Sie schreiben und aus dem Sie lesen können, ein Objekt, dem ein ganz bestimmter Status zugeordnet ist. Diese Objekte existieren, weil sie für einige Dinge wie asynchrone E / A, Codierungen usw. sehr nützlich sind.
UPDATE4
Eines der ersten Dinge, die ich versuchte, war das gleiche wie der unten angegebene Vorschlag (überprüfen Sie die akzeptierte Antwort), aber es erwies sich als zu zerbrechlich.
Angenommen, Sie haben eine Klasse, die den Typ zurückgeben muss:
public <RW extends Readable & Writable> RW getItAll();
Wenn Sie diese Methode aufrufen, wird die generische RW durch die Variable bestimmt, die das Objekt empfängt. Sie benötigen daher eine Möglichkeit, diese Variable zu beschreiben.
MyObject myObject = someInstance.getItAll();
Dies funktioniert, bindet es jedoch erneut an eine Implementierung und kann zur Laufzeit tatsächlich Ausnahmen für Klassencasts auslösen (je nachdem, was zurückgegeben wird).
Wenn Sie außerdem eine Klassenvariable vom Typ RW möchten, müssen Sie die generische Variable auf Klassenebene definieren.
quelle
Antworten:
Ja, Sie können Ihren Methodenparameter als einen unterbestimmten Typ deklarieren, der sowohl Folgendes umfasst
Readable
als auchWritable
:Die Methodendeklaration sieht schrecklich aus, aber die Verwendung ist einfacher, als über die einheitliche Schnittstelle Bescheid zu wissen.
quelle
process(Readable readThing, Writable writeThing)
und wenn Sie ihn mit aufrufen müssenprocess(foo, foo)
.<RW extends Readable&Writable>
?process
dies mehrere verschiedene Dinge tut und gegen das Prinzip der einfachen Verantwortung verstößt.Wenn es einen Ort , wo Sie brauchen ,
myObject
da beide einReadable
undWritable
Sie können:Diesen Ort umgestalten? Lesen und Schreiben sind zwei verschiedene Dinge. Wenn eine Methode beides tut, folgt sie möglicherweise nicht dem Prinzip der Einzelverantwortung.
Übergeben Sie
myObject
zweimal alsReadable
und alsWritable
(zwei Argumente). Was kümmert es die Methode, ob es sich um dasselbe Objekt handelt oder nicht?quelle
StreamWriter
vs.StreamReader
(und viele andere)Keine der Antworten behandelt derzeit die Situation, in der Sie keine lesbaren oder beschreibbaren, sondern beide benötigen . Sie benötigen Garantien, dass Sie beim Schreiben nach A diese Daten von A zurücklesen können, nicht nach A und von B lesen und nur hoffen, dass sie tatsächlich dasselbe Objekt sind. Anwendungsfälle sind reichlich vorhanden, beispielsweise überall dort, wo Sie einen ByteBuffer verwenden würden.
Wie auch immer, ich bin fast fertig mit dem Modul, an dem ich arbeite, und habe mich derzeit für die Wrapper-Oberfläche entschieden:
Jetzt können Sie zumindest Folgendes tun:
Meine eigenen Implementierungen von container (derzeit 3) implementieren alle Container im Gegensatz zu den separaten Schnittstellen. Sollte dies jedoch jemand in einer Implementierung vergessen, stellt IOUtils eine Dienstprogrammmethode bereit:
Ich weiß, dass dies nicht die optimale Lösung ist, aber es ist im Moment immer noch der beste Ausweg, da Container immer noch ein ziemlich großer Anwendungsfall ist.
quelle
Bei einer generischen MyObject-Instanz müssen Sie immer wissen, ob sie Lese- oder Schreibvorgänge unterstützt. Sie hätten also Code wie:
Im einfachen Fall glaube ich nicht, dass Sie dies verbessern können. Aber wenn
readThisReadable
will schreiben die Lesbar auf eine andere Datei , nachdem sie lesen, wird es peinlich.Also würde ich wahrscheinlich so weitermachen:
Wird dies als Parameter verwendet,
readThisReadable
kann jetztreadThisWholeShabam
jede Klasse, die TheWholeShabam implementiert, verarbeitet werden, nicht nur MyObject. Und es kann es schreiben, wenn es beschreibbar ist und nicht, wenn es nicht beschreibbar ist. (Wir haben echten "Polymorphismus".)Der erste Satz Code wird also:
Und Sie können hier eine Zeile speichern, indem Sie readThisWholeShebam () die Lesbarkeitsprüfung durchführen lassen.
Dies bedeutet, dass unser früherer Readable-only-Server isWriteable () implementieren muss ( false zurückgeben ) und write () (nichts tun), aber jetzt kann er alle Arten von Stellen durchlaufen, die vorher nicht möglich waren, und den gesamten Code, der TheWholeShabam verarbeitet die objekte werden sich ohne weiteren aufwand unserer seite darum kümmern.
Eine andere Sache: Wenn Sie mit einem Aufruf von read () in einer Klasse, die nicht liest, und einem Aufruf von write () in einer Klasse, die nicht schreibt, ohne etwas in den Papierkorb zu werfen, fertig sind, können Sie isReadable () und isWriteable überspringen () Methoden. Dies wäre die eleganteste Art, damit umzugehen - wenn es funktioniert.
quelle