c# discriminated - Was ist der Zweck einer Marker-Schnittstelle?




5 Answers

Dies ist ein bisschen eine Tangente basierend auf der Antwort von "Mitch Wheat".

Im Allgemeinen, immer wenn ich sehe, dass Leute die Rahmenentwurfsrichtlinien zitieren, möchte ich immer erwähnen, dass:

Sie sollten die Richtlinien für den Framework-Entwurf meistens ignorieren.

Dies liegt nicht an einem Problem mit den Framework-Design-Richtlinien. Ich denke, das .NET-Framework ist eine fantastische Klassenbibliothek. Viele dieser Fantasien ergeben sich aus den Richtlinien für das Framework-Design.

Die Entwurfsrichtlinien gelten jedoch nicht für den meisten von den meisten Programmierern geschriebenen Code. Ihr Zweck ist es, die Erstellung eines großen Frameworks zu ermöglichen, das von Millionen von Entwicklern verwendet wird, um das Schreiben von Bibliotheken nicht effizienter zu gestalten.

Viele der darin enthaltenen Vorschläge können Ihnen helfen, Dinge zu tun, die:

  1. Vielleicht nicht die einfachste Art, etwas zu implementieren
  2. Kann zu zusätzlicher Code-Duplizierung führen
  3. Kann zusätzlichen Laufzeitaufwand haben

Das .net-Framework ist groß, sehr groß. Es ist so groß, dass es absolut unvernünftig wäre anzunehmen, dass jemand detaillierte Kenntnisse über jeden Aspekt davon hat. In der Tat ist es viel sicherer anzunehmen, dass die meisten Programmierer häufig Teile des Frameworks treffen, die sie nie zuvor benutzt haben.

In diesem Fall sind die primären Ziele eines API-Designers:

  1. Halten Sie die Dinge im Einklang mit dem Rest des Frameworks
  2. Beseitigen Sie unnötige Komplexität in der API-Oberfläche

Die Framework-Design-Richtlinien bringen Entwickler dazu, Code zu erstellen, der diese Ziele erreicht.

Das bedeutet Dinge wie das Vermeiden von Vererbungsebenen zu tun, auch wenn es bedeutet, Code zu duplizieren oder den gesamten Exception-Code auf "Einstiegspunkte" zu setzen, anstatt gemeinsame Helfer zu verwenden (so dass Stack-Traces im Debugger mehr Sinn ergeben) von anderen ähnlichen Dingen.

Der Hauptgrund dafür, dass diese Richtlinien die Verwendung von Attributen anstelle von Markierungsschnittstellen vorschlagen, liegt darin, dass das Entfernen der Markierungsschnittstellen die Vererbungsstruktur der Klassenbibliothek viel zugänglicher macht. Ein Klassendiagramm mit 30 Typen und 6 Ebenen der Vererbungshierarchie ist im Vergleich zu einem mit 15 Typen und 2 Hierarchieebenen sehr entmutigend.

Wenn es wirklich Millionen von Entwicklern gibt, die Ihre APIs verwenden, oder wenn Ihre Codebasis wirklich groß ist (sagen wir über 100K LOC), dann kann das Befolgen dieser Richtlinien sehr hilfreich sein.

Wenn 5 Millionen Entwickler 15 Minuten mit dem Lernen einer API verbringen und nicht 60 Minuten, um sie zu lernen, ergibt sich eine Nettoersparnis von 428 Mannjahren. Das ist eine Menge Zeit.

Die meisten Projekte beinhalten jedoch nicht Millionen von Entwicklern oder 100K + LOC. In einem typischen Projekt mit 4 Entwicklern und rund 50K Loks sind die Annahmen sehr unterschiedlich. Die Entwickler im Team werden viel besser verstehen, wie der Code funktioniert. Das bedeutet, dass es viel sinnvoller ist, für die schnelle Erstellung qualitativ hochwertigen Codes zu optimieren und die Anzahl der Fehler und den Aufwand für Änderungen zu reduzieren.

Wenn Sie 1 Woche Entwicklungscode ausgeben, der mit dem .net-Framework konsistent ist, können Sie mit 8 Stunden Code schreiben, der leicht zu ändern ist und weniger Bugs aufweist:

  1. Späte Projekte
  2. Niedrigere Boni
  3. Erhöhte Fehleranzahl
  4. Mehr Zeit im Büro und weniger Zeit am Strand, um Margaritas zu trinken.

Ohne 4.999.999 andere Entwickler die Kosten zu übernehmen ist es normalerweise nicht wert.

Das Testen auf Marker-Interfaces führt zum Beispiel zu einem einzelnen "is" -Ausdruck und führt zu weniger Code, der nach Attributen sucht.

Also mein Rat ist:

  1. Befolgen Sie die Rahmenrichtlinien religiös, wenn Sie Klassenbibliotheken (oder UI-Widgets) entwickeln, die für einen weit verbreiteten Konsum gedacht sind.
  2. Überlegen Sie einige davon, wenn Sie über 100K LOC in Ihrem Projekt haben
  3. Ansonsten ignoriere sie komplett.
union sum

Was ist der Zweck einer Marker-Schnittstelle?




Da jede andere Antwort besagt, dass sie vermieden werden sollten, wäre es nützlich, eine Erklärung dafür zu haben.

Erstens, warum Marker-Interfaces verwendet werden: Sie existieren, um dem Code, der das Objekt verwendet, das ihn implementiert, zu erlauben, zu überprüfen, ob er diese Schnittstelle implementiert und das Objekt anders behandelt, wenn dies der Fall ist.

Das Problem bei diesem Ansatz besteht darin, dass die Kapselung aufgehoben wird. Das Objekt selbst hat jetzt eine indirekte Kontrolle darüber, wie es extern verwendet wird. Darüber hinaus verfügt es über Kenntnisse des Systems, in dem es verwendet wird. Durch die Anwendung der Marker-Schnittstelle schlägt die Klassendefinition vor, dass sie irgendwo verwendet werden soll, um die Existenz des Markers zu überprüfen. Es hat implizites Wissen über die Umgebung, in der es verwendet wird und versucht zu definieren, wie es verwendet werden soll. Dies steht der Idee der Kapselung entgegen, weil sie Kenntnis von der Implementierung eines Teils des Systems hat, der vollständig außerhalb seines eigenen Umfangs existiert.

Auf praktischer Ebene verringert dies die Portabilität und Wiederverwendbarkeit. Wenn die Klasse in einer anderen Anwendung wiederverwendet wird, muss die Schnittstelle ebenfalls kopiert werden. In der neuen Umgebung hat sie möglicherweise keine Bedeutung, sodass sie vollständig redundant ist.

Der "Marker" ist also Metadaten über die Klasse. Diese Metadaten werden nicht von der Klasse selbst verwendet und sind nur für (manchen!) Externen Client-Code von Bedeutung, so dass sie das Objekt auf eine bestimmte Art und Weise behandeln können. Da die Metadaten nur für den Client-Code relevant sind, sollten sie sich im Client-Code und nicht in der Klassen-API befinden.

Der Unterschied zwischen einer "Marker-Schnittstelle" und einer normalen Schnittstelle besteht darin, dass eine Schnittstelle mit Methoden der Außenwelt sagt, wie sie verwendet werden kann, während eine leere Schnittstelle bedeutet, dass sie der Außenwelt sagt, wie sie verwendet werden soll.




Marker-Interfaces können manchmal ein notwendiges Übel sein, wenn eine Sprache diskriminierte Unionstypen nicht unterstützt.

Angenommen, Sie möchten eine Methode definieren, die ein Argument erwartet, dessen Typ genau A, B oder C sein muss. In vielen funktionalen Erstsprachen (wie F# ) kann ein solcher Typ sauber definiert werden als:

type Arg = 
    | AArg of A 
    | BArg of B 
    | CArg of C

In OO-ersten Sprachen wie C # ist dies jedoch nicht möglich. Die einzige Möglichkeit, etwas Ähnliches zu erreichen, besteht darin, die Schnittstelle IArg zu definieren und A, B und C damit zu markieren.

Natürlich könnten Sie die Verwendung der Marker-Schnittstelle vermeiden, indem Sie einfach den Typ "Objekt" als Argument akzeptieren, aber dann würden Sie an Aussagekraft und Sicherheit verlieren.

Diskriminierte Unionstypen sind äußerst nützlich und existieren in funktionalen Sprachen seit mindestens 30 Jahren. Seltsamerweise haben bis heute alle gängigen OO-Sprachen diese Eigenschaft ignoriert - obwohl sie eigentlich nichts mit funktionaler Programmierung per se zu tun hat, sondern zum Typensystem gehört.




Diese beiden Erweiterungsmethoden werden die meisten Probleme lösen, die Scott gegenüber den Attributen von favormarker-Schnittstellen geltend macht:

public static bool HasAttribute<T>(this ICustomAttributeProvider self)
    where T : Attribute
{
    return self.GetCustomAttributes(true).Any(o => o is T);
}

public static bool HasAttribute<T>(this object self)
    where T : Attribute
{
    return self != null && self.GetType().HasAttribute<T>()
}

Jetzt hast du:

if (o.HasAttribute<FooAssignableAttribute>())
{
    //...
}

gegen:

if (o is IFooAssignable)
{
    //...
}

Ich sehe nicht, wie das Erstellen einer API mit dem ersten Muster fünfmal so lange dauert wie das zweite, wie Scott behauptet.




Die Markierungsschnittstelle ist eine vollständig leere Schnittstelle, die keine body / data-members / Implementierung hat.
Eine Klasse implementiert bei Bedarf eine Markierungsschnittstelle, sie dient lediglich zum " Markieren ". bedeutet, dass es der JVM sagt, dass die bestimmte Klasse zum Zweck des Klonens ist, so dass es klonen kann. Diese spezielle Klasse dient dazu, ihre Objekte zu serialisieren, damit ihre Objekte serialisiert werden können.




Related