Leere Interfaces werden, soweit ich das beurteilen kann, im Allgemeinen als schlechte Praxis eingestuft - insbesondere, wenn Dinge wie Attribute von der Sprache unterstützt werden.
Wird eine Schnittstelle jedoch als "leer" betrachtet, wenn sie von anderen Schnittstellen erbt?
interface I1 { ... }
interface I2 { ... } //unrelated to I1
interface I3
: I1, I2
{
// empty body
}
Alles , was die Geräte I3
benötigen zu implementieren I1
und I2
, und Objekte aus verschiedenen Klassen , die erben I3
kann dann untereinander ausgetauscht werden (siehe unten), so ist es richtig zu nennen I3
leer ? Wenn ja, was wäre ein besserer Weg, dies zu architektonisieren?
// with I3 interface
class A : I3 { ... }
class B : I3 { ... }
class Test {
void foo() {
I3 something = new A();
something = new B();
something.SomeI1Function();
something.SomeI2Function();
}
}
// without I3 interface
class A : I1, I2 { ... }
class B : I1, I2 { ... }
class Test {
void foo() {
I1 something = new A();
something = new B();
something.SomeI1Function();
something.SomeI2Function(); // we can't do this without casting first
}
}
foo
? (Wenn die Antwort "Ja" lautet, fahren Sie fort!)var : I1, I2 something = new A();
für lokale Variablen nett (wenn wir die guten Punkte, die in Konrads Antwort aufgeworfen wurden, ignorieren)Antworten:
"Bundling"
I1
und "I2
into"I3
bieten eine nette (?) Verknüpfung oder eine Art Syntaxzucker für alle Anwendungen, für die Sie beide implementieren möchten.Das Problem bei diesem Ansatz ist , dass Sie nicht andere Entwickler von explizit Implementierung stoppen können
I1
undI2
nebeneinander, aber es funktioniert nicht in beide Richtungen - während: I3
entspricht der: I1, I2
,: I1, I2
ist kein Äquivalent: I3
. Auch wenn sie funktional identisch sind, eine Klasse, die beide unterstütztI1
undI2
nicht als unterstützend erkannt wirdI3
.Dies bedeutet, dass Sie zwei Möglichkeiten anbieten, um dasselbe Ziel zu erreichen: "manuell" und "süß" (mit Ihrem Syntaxzucker). Dies kann sich negativ auf die Codekonsistenz auswirken. Das Anbieten von 2 verschiedenen Möglichkeiten, um das Gleiche hier, dort und an einer anderen Stelle zu erreichen, führt bereits zu 8 möglichen Kombinationen :)
OK, das kannst du nicht. Aber vielleicht ist es tatsächlich ein gutes Zeichen?
Wenn
I1
undI2
implizieren unterschiedliche Verantwortlichkeiten (ansonsten wäre es gar nicht nötig, sie zu trennen), dann ist es vielleichtfoo()
falschsomething
, zwei unterschiedliche Verantwortlichkeiten auf einmal ausführen zu lassen?Mit anderen Worten, es könnte sein, dass dies
foo()
selbst gegen das Prinzip der Einzelverantwortung verstößt, und die Tatsache, dass Casting und Laufzeit-Typprüfungen erforderlich sind, ist eine rote Fahne, die uns alarmiert.BEARBEITEN:
Wenn Sie darauf bestehen möchten,
foo
dass für die Implementierung vonI1
und Folgendes erforderlich istI2
, können Sie diese als separate Parameter übergeben:Und sie könnten dasselbe Objekt sein:
Was
foo
kümmert es, ob sie dieselbe Instanz sind oder nicht?Aber wenn es Ihnen etwas ausmacht (zB weil sie nicht staatenlos sind), oder wenn Sie einfach denken, dass dies hässlich aussieht, könnten Sie stattdessen Generika verwenden:
Jetzt sorgen allgemeine Beschränkungen für den gewünschten Effekt, ohne die Außenwelt mit irgendetwas zu verschmutzen. Dies ist ein idiomatisches C #, das auf Prüfungen zur Kompilierungszeit basiert und sich insgesamt besser anfühlt.
Ich sehe
I3 : I2, I1
etwas als Versuch zu emulieren Union - Typ in einer Sprache , die sie nicht von der Box unterstützt aus (C # oder Java nicht, während Union - Typen in funktionalen Sprachen existieren).Der Versuch, ein Konstrukt zu emulieren, das von der Sprache nicht wirklich unterstützt wird, ist oft umständlich. Ebenso können Sie in C # Erweiterungsmethoden und Schnittstellen verwenden, wenn Sie Mixins emulieren möchten . Möglich? Ja. Gute Idee? Ich bin mir nicht sicher.
quelle
Wenn es besonders wichtig war, dass eine Klasse beide Schnittstellen implementiert, sehe ich nichts falsches daran, eine dritte Schnittstelle zu erstellen, die von beiden erbt. Ich würde jedoch darauf achten, nicht in die Falle der Überentwicklung zu geraten. Eine Klasse könnte von der einen oder der anderen oder von beiden erben. Wenn mein Programm in diesen drei Fällen nicht viele Klassen hatte, ist es ein wenig schwierig, eine Schnittstelle für beide zu erstellen, und schon gar nicht, wenn diese beiden Schnittstellen keine Beziehung zueinander hatten (bitte keine IComparableAndSerializable-Schnittstellen).
Ich erinnere Sie daran, dass Sie auch eine abstrakte Klasse erstellen können, die von beiden Schnittstellen erbt, und dass Sie diese abstrakte Klasse anstelle von I3 verwenden können. Ich denke, es wäre falsch zu sagen, dass Sie es nicht tun sollten, aber Sie sollten zumindest einen guten Grund haben.
quelle
Ich stimme dir nicht zu. Lesen Sie mehr über Marker-Interfaces .
quelle
instanceof
anstatt Polymorphismus zu verwenden