Riechen leere Schnittstellen nach Code? [geschlossen]

76

Ich habe eine Funktion, die dieselbe Art von Objekten zurückgibt (Abfrageergebnisse), aber keine gemeinsamen Eigenschaften oder Methoden hat. Um einen gemeinsamen Typ zu haben, habe ich eine leere Schnittstelle als Rückgabetyp verwendet und diese auf beiden "implementiert".

Das klingt natürlich nicht richtig. Ich kann mich nur trösten, indem ich mich an die Hoffnung klammere, dass diese Klassen eines Tages etwas gemeinsam haben und ich diese gemeinsame Logik auf meine leere Oberfläche verschieben werde. Dennoch bin ich nicht zufrieden und denke darüber nach, ob ich zwei verschiedene Methoden haben und als nächstes bedingt anrufen soll. Wäre das ein besserer Ansatz?

Mir wurde auch gesagt, dass .NET Framework leere Schnittstellen für Tagging-Zwecke verwendet.

Meine Frage ist: Ist eine leere Schnittstelle ein starkes Zeichen für ein Designproblem oder wird sie häufig verwendet?

EDIT : Für Interessierte fand ich später heraus, dass diskriminierte Gewerkschaften in funktionalen Sprachen die perfekte Lösung für das sind, was ich erreichen wollte. C # scheint diesem Konzept noch nicht freundlich zu sein.

EDIT : Ich habe ein längeres Stück über dieses Problem geschrieben und das Problem und die Lösung im Detail erklärt.

Sedat Kapanoglu
quelle
15
Diese werden als Marker-Schnittstellen bezeichnet und sind anscheinend weit verbreitet.
BoltClock
3
Lesen Sie diese msdn.microsoft.com/en-us/library/ms182128%28v=vs.80%29.aspx (meine Meinung muss andere Methoden sein)
V4Vendetta
1
Etwas in ähnlichen Zeilen stackoverflow.com/questions/835140/…
V4Vendetta
9
Kann ein Code-Geruch ein schöner Geruch sein, in welchem ​​Fall ist er gut? Kaffee, Waffeln und Speck zum Beispiel
Chris S
Wissen Sie beim Aufrufen Ihrer Funktion bereits anhand der von Ihnen angegebenen Parameter, welche Art von Objekt zurückkommt? In diesem Fall wäre es korrekter, wenn mehrere Funktionen den richtigen Typ zurückgeben würden.
Angel O'Sphere

Antworten:

48

Obwohl es den Anschein hat, dass es für diesen Anwendungsfall ein Entwurfsmuster gibt (viele haben jetzt "Marker-Schnittstelle" erwähnt), glaube ich, dass die Verwendung einer solchen Praxis ein Hinweis auf einen Code-Geruch ist (zumindest die meiste Zeit).

Wie @ V4Vendetta veröffentlicht hat, gibt es eine statische Analyseregel, die darauf abzielt: http://msdn.microsoft.com/en-us/library/ms182128(v=VS.100).aspx

Wenn Ihr Entwurf leere Schnittstellen enthält, deren Implementierung von Typen erwartet wird, verwenden Sie wahrscheinlich eine Schnittstelle als Markierung oder als Methode zur Identifizierung einer Gruppe von Typen. Wenn diese Identifizierung zur Laufzeit erfolgt, können Sie dies mithilfe eines benutzerdefinierten Attributs erreichen. Verwenden Sie das Vorhandensein oder Fehlen des Attributs oder die Eigenschaften des Attributs, um die Zieltypen zu identifizieren. Wenn die Identifizierung zur Kompilierungszeit erfolgen muss, kann eine leere Schnittstelle verwendet werden.

Dies ist die zitierte MSDN-Empfehlung:

Entfernen Sie die Schnittstelle oder fügen Sie Mitglieder hinzu. Wenn die leere Schnittstelle zum Beschriften einer Reihe von Typen verwendet wird, ersetzen Sie die Schnittstelle durch ein benutzerdefiniertes Attribut.

Dies spiegelt auch den Abschnitt "Kritik" des bereits veröffentlichten Wikipedia-Links wider.

Ein Hauptproblem bei Markierungsschnittstellen besteht darin, dass eine Schnittstelle einen Vertrag zum Implementieren von Klassen definiert und dieser Vertrag von allen Unterklassen geerbt wird. Dies bedeutet, dass Sie einen Marker nicht "implementieren" können. Wenn Sie im angegebenen Beispiel eine Unterklasse erstellen, die Sie nicht serialisieren möchten (möglicherweise weil dies vom Übergangszustand abhängt), müssen Sie explizit NotSerializableException (gemäß ObjectOutputStream-Dokumenten) auslösen.

UrbanEsc
quelle
1
Ich denke in meinem Anwendungsfall (nur zwei Klassen und wird wahrscheinlich nicht abgeleitet), scheint es in Ordnung zu sein. Siehe meine Klarstellung bearbeiten.
Sedat Kapanoglu
Ich akzeptiere das als Antwort. Obwohl andere Antworten ebenfalls wertvolle Informationen liefern, werden in dieser Antwort die potenziellen Probleme des Ansatzes am ausführlichsten erläutert.
Sedat Kapanoglu
1
Wie bereits an anderer Stelle erwähnt, attributessind sie zwar die „richtige“ Vorgehensweise, aber schwieriger zu implementieren und zu verwenden, was in der Praxis ihrer Verwendung entgegenwirkt.
nicodemus13
9

Sie geben an, dass Ihre Funktion "in bestimmten Fällen völlig unterschiedliche Objekte zurückgibt" - aber wie unterschiedlich sind sie? Könnte einer ein Stream-Writer sein, ein anderer eine UI-Klasse, ein anderer ein Datenobjekt? Nein ... ich bezweifle es!

Ihre Objekte haben möglicherweise keine gemeinsamen Methoden oder Eigenschaften, sie ähneln sich jedoch wahrscheinlich in ihrer Rolle oder Verwendung. In diesem Fall erscheint eine Markierungsschnittstelle völlig angemessen.

ColinE
quelle
Es handelt sich um verschiedene Arten von Abfrageergebnissen, aber beide Abfrageergebnisse, ja.
Sedat Kapanoglu
1
OK - wie ich dachte, haben sie eine gemeinsame Rolle, obwohl sie keine gemeinsamen Eigenschaften enthalten. Klingt so, als wäre eine Marker-Oberfläche genau richtig!
ColinE
8

Wenn nicht als Marker-Schnittstelle verwendet , würde ich sagen, dass dies ein Code-Geruch ist.

Eine Schnittstelle definiert einen Vertrag, an den sich der Implementierer hält. Wenn Sie leere Schnittstellen haben, über die Sie keine Reflexion verwenden (wie dies bei Markierungsschnittstellen der Fall ist), können Sie auch Objectden (bereits vorhandenen) Basistyp verwenden.

Oded
quelle
2
objectwäre zu allgemein und würde keinen Hinweis auf die Rückgabe "Art" geben. Die Schnittstelle hilft mir, meine Optionen für die Interpretation des Rückgabewerts der Funktion einzugrenzen. Ich verstehe jedoch Ihren Kommentar in Bezug auf die mangelnde Klärung der Ähnlichkeit der von mir zurückgegebenen Objekte.
Sedat Kapanoglu
@ssg - ganz. Es muss etwas ähnliches mit diesen Objekten , wenn man sich auf eine Methode übergeben , auf sie zu arbeiten, sonst sollten Sie separate Methoden.
Oded
6

Sie haben Ihre eigene Frage beantwortet ... "Ich habe eine Funktion, die in bestimmten Fällen völlig unterschiedliche Objekte zurückgibt." ... Warum möchten Sie dieselbe Funktion haben, die völlig unterschiedliche Objekte zurückgibt? Ich sehe keinen Grund dafür, dass dies nützlich ist. Vielleicht haben Sie einen guten. In diesem Fall teilen Sie uns dies bitte mit.

EDIT: In Anbetracht Ihrer Klarstellung sollten Sie in der Tat eine Marker-Schnittstelle verwenden. "völlig anders" ist ganz anders als "sind die gleiche Art". Wenn sie völlig anders wären (nicht nur, dass sie keine gemeinsamen Mitglieder haben), wäre das ein Code-Geruch.

Luchian Grigore
quelle
Dies scheint ein besserer Kandidat für einen Kommentar zu sein. Ich habe meiner Frage einen Abschnitt zur Klärung hinzugefügt. Fragen Sie weg, wenn Sie noch etwas brauchen.
Sedat Kapanoglu
4

Wie viele wahrscheinlich bereits gesagt haben, hat eine leere Schnittstelle eine gültige Verwendung als "Markierungsschnittstelle".

Die wahrscheinlich beste Verwendung, die ich mir vorstellen kann, besteht darin, ein Objekt als zu einer bestimmten Teilmenge der Domäne gehörend zu bezeichnen, die von einem entsprechenden Repository verarbeitet wird. Angenommen, Sie haben verschiedene Datenbanken, aus denen Sie Daten abrufen, und Sie haben jeweils eine Repository-Implementierung. Ein bestimmtes Repository kann nur eine Teilmenge verarbeiten und sollte keine Instanz eines Objekts aus einer anderen Teilmenge erhalten. Ihr Domain-Modell könnte folgendermaßen aussehen:

//Every object in the domain has an identity-sourced Id field
public interface IDomainObject
{
   long Id{get;}
}

//No additional useful information other than this is an object from the user security DB
public interface ISecurityDomainObject:IDomainObject {}

//No additional useful information other than this is an object from the Northwind DB
public interface INorthwindDomainObject:IDomainObject {}


//No additional useful information other than this is an object from the Southwind DB
public interface ISouthwindDomainObject:IDomainObject {}

Ihre Repositorys können dann generisch für ISecurityDomainObject, INorthwindDomainObject und ISouthwindDomainObject erstellt werden. Anschließend wird zur Kompilierungszeit überprüft, ob Ihr Code nicht versucht, ein Sicherheitsobjekt an die Northwind-Datenbank (oder eine andere Permutation) zu übergeben. In solchen Situationen bietet die Schnittstelle wertvolle Informationen zur Art der Klasse, auch wenn sie keinen Implementierungsvertrag enthält.

KeithS
quelle