it-swarm.com.de

Was ist der Zweck einer Marker-Schnittstelle?

Was ist der Zweck einer Marker-Schnittstelle?

79
coder

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

Immer wenn ich sehe, dass Leute die Richtlinien für das Rahmendesign zitieren, erwähne ich das immer gerne:

Sie sollten die Richtlinien für die Rahmengestaltung meistens ignorieren.  

Dies ist nicht auf ein Problem mit den Rahmenentwurfsrichtlinien zurückzuführen. Ich denke, das .NET-Framework ist eine fantastische Klassenbibliothek. Ein Großteil dieser Phantastik ergibt sich aus den Richtlinien für die Rahmengestaltung. 

Die Entwurfsrichtlinien gelten jedoch nicht für die meisten Codes, die von den meisten Programmierern geschrieben werden. Ihr Zweck besteht darin, die Schaffung eines großen Frameworks zu ermöglichen, das von Millionen von Entwicklern verwendet wird, und nicht, um das Schreiben von Bibliotheken effizienter zu gestalten.

Viele der darin enthaltenen Vorschläge können Sie dabei unterstützen, Folgendes zu tun:

  1. Vielleicht nicht der einfachste Weg, etwas zu implementieren
  2. Kann zu zusätzlicher Code-Duplizierung führen
  3. Eventuell zusätzlicher Laufzeitaufwand

Das .NET-Framework ist groß, wirklich groß. Es ist so groß, dass es absolut unvernünftig wäre anzunehmen, dass jeder über jeden Aspekt detailliert Bescheid weiß. In der Tat ist es viel sicherer anzunehmen, dass die meisten Programmierer häufig auf Teile des Frameworks stoßen, die sie noch nie verwendet haben.

In diesem Fall bestehen die Hauptziele eines API-Designers darin,

  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 Bitten Sie die Entwickler, Code zu erstellen, der diese Ziele erreicht.

Dies bedeutet, dass Sie beispielsweise Vererbungsebenen vermeiden, selbst wenn Code dupliziert wird, oder dass alle Ausnahmen Code an "Einstiegspunkte" ausgeben, anstatt gemeinsam genutzte Helfer zu verwenden (so dass Stapelverfolgungen im Debugger mehr Sinn machen) und vieles von anderen ähnlichen Dingen.

Der Hauptgrund, den diese Richtlinien für die Verwendung von Attributen anstelle von Markierungsschnittstellen vorschlagen, besteht darin, dass durch das Entfernen der Markierungsschnittstellen die Vererbungsstruktur der Klassenbibliothek wesentlich zugänglicher wird. Ein Klassendiagramm mit 30 Typen und 6 Schichten der Vererbungshierarchie ist im Vergleich zu einem Diagramm mit 15 Typen und 2 Hierarchieebenen sehr entmutigend. 

Wenn wirklich Millionen von Entwicklern Ihre APIs verwenden oder Ihre Codebasis wirklich groß ist (beispielsweise über 100.000 LOC), kann das Befolgen dieser Richtlinien sehr hilfreich sein.

Wenn 5 Millionen Entwickler 15 Minuten damit verbringen, eine API zu lernen, anstatt 60 Minuten, um sie zu lernen, ist das Ergebnis eine Nettoeinsparung von 428 Mannjahren. Das ist viel Zeit.

Bei den meisten Projekten sind jedoch keine Millionen Entwickler oder 100.000 LOC beteiligt. In einem typischen Projekt mit etwa 4 Entwicklern und etwa 50.000 Loks unterscheiden sich die Annahmen erheblich. Die Entwickler des Teams haben ein besseres Verständnis für die Funktionsweise des Codes. Das bedeutet, dass es viel sinnvoller ist, schnell zu optimieren, um qualitativ hochwertigen Code zu erzeugen und die Anzahl der Fehler und den Aufwand für Änderungen zu reduzieren. 

Wenn Sie 1 Woche lang an der Entwicklung von Code arbeiten, der mit dem .NET-Framework konsistent ist, müssen Sie 8 Stunden Code schreiben, der leicht zu ändern ist und weniger Fehler aufweist.

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

Ohne 4.999.999 andere Entwickler, um die Kosten aufzufangen, lohnt es sich normalerweise nicht.

Das Testen auf Markierungsschnittstellen führt beispielsweise zu einem einzigen "is" -Ausdruck und führt zu weniger Code als bei der Suche nach Attributen. 

Also mein Rat ist:

  1. Befolgen Sie die Rahmenrichtlinien streng, wenn Sie Klassenbibliotheken (oder UI-Widgets) entwickeln, die für die breite Verbreitung gedacht sind.
  2. Erwägen Sie, einige davon zu übernehmen, wenn Sie über 100.000 LOC in Ihrem Projekt haben
  3. Ansonsten ignorieren Sie sie vollständig.
71

Markierungsschnittstellen werden verwendet, um die Fähigkeit einer Klasse als Implementierung einer bestimmten Schnittstelle zur Laufzeit zu kennzeichnen. 

Die Richtlinien Interface Design und .NET Type Design Guidelines - Interface Design raten der Verwendung von Markerschnittstellen zugunsten der Verwendung von Attributen in C # ab. Wie @Jay Bazuzi feststellt, ist es jedoch einfacher, nach Markerschnittstellen zu suchen als für Attribute: o is I 

Also stattdessen:

public interface IFooAssignable {} 

public class FooAssignableAttribute : IFooAssignable 
{
    ...
}

Die .NET-Richtlinien empfehlen, dass Sie Folgendes tun:

public class FooAssignableAttribute : Attribute 
{
    ...
}

[FooAssignable]
public class Foo 
{    
   ...
} 
41
Mitch Wheat

Da in jeder anderen Antwort angegeben wurde, dass sie vermieden werden sollten, wäre es hilfreich, eine Erklärung zu haben, warum.

Erstens, warum Marker-Interfaces verwendet werden: Sie existieren, um dem Code, der das Objekt verwendet, das es implementiert, zu ermöglichen, zu prüfen, ob sie das Interface implementieren, und um das Objekt anders zu behandeln, wenn dies der Fall ist.

Das Problem bei diesem Ansatz ist, dass die Kapselung unterbrochen wird. Das Objekt selbst hat jetzt indirekte Kontrolle darüber, wie es extern verwendet wird. Darüber hinaus hat es Kenntnisse über das System, in dem es verwendet werden soll. Durch Anwenden der Markierungsschnittstelle schlägt die Klassendefinition vor, dass es an einem Ort verwendet werden soll, der die Existenz der Markierung überprüft. Es hat implizites Wissen über die Umgebung, in der es verwendet wird, und versucht zu definieren, wie es verwendet werden soll. Dies widerspricht der Idee der Verkapselung, da es Kenntnisse über die Implementierung eines Teils des Systems hat, der vollständig außerhalb seines eigenen Bereichs existiert.

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

Als solches sind die "Marker" Metadaten über die Klasse. Diese Metadaten werden von der Klasse selbst nicht verwendet und sind nur für (einige!) Externen Client-Code von Bedeutung, damit er das Objekt auf eine bestimmte Weise behandeln kann. Da dies nur für den Clientcode von Bedeutung ist, sollten sich die Metadaten im Clientcode befinden, nicht in der Klassen-API.

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 soll kann, während eine leere Schnittstelle impliziert, dass sie der Außenwelt sagt, wie sie funktioniert sollte verwendet werden.

22
Tom B

Marker-Interfaces können manchmal ein notwendiges Übel sein, wenn eine Sprache keine diskriminierten Union -Typen 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-Erstsprachen wie C # ist dies jedoch nicht möglich. Der einzige Weg, etwas Ähnliches zu erreichen, besteht darin, das Interface IArg zu definieren und A, B und C damit zu "markieren". 

Natürlich können Sie die Verwendung der Markierungsschnittstelle vermeiden, indem Sie einfach den Typ "object" als Argument akzeptieren, aber dann würden Sie die Ausdruckskraft und ein gewisses Maß an Typensicherheit verlieren. 

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

7
Marc Sigrist

Eine Markierungsschnittstelle ist nur eine Schnittstelle, die leer ist. Eine Klasse würde diese Schnittstelle als Metadaten implementieren, die aus irgendeinem Grund verwendet werden sollen. In C # würden Sie Attribute häufig verwenden, um eine Klasse aus denselben Gründen zu markieren, in denen Sie eine Marker-Schnittstelle in anderen Sprachen verwenden.

6
Richard Hein

Mit diesen beiden Erweiterungsmethoden werden die meisten Probleme gelöst, die Scott bevorzugt, Markierungsschnittstellen gegenüber Attributen:

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 kann nicht erkennen, wie das Erstellen einer API im Vergleich zum zweiten Muster fünfmal so lange dauert, wie Scott behauptet.

4
Mr Anderson

Mit einer Markierungsschnittstelle kann eine Klasse so markiert werden, dass sie auf alle abgeleiteten Klassen angewendet wird. Eine "reine" Markierungsschnittstelle würde nichts definieren oder erben; Eine nützlichere Art von Markierungsschnittstellen kann eine sein, die eine andere Schnittstelle "erbt", jedoch keine neuen Mitglieder definiert. Wenn es beispielsweise eine Schnittstelle "IReadableFoo" gibt, könnte man auch eine Schnittstelle "IImmutableFoo" definieren, die sich wie ein "Foo" verhalten würde, aber jedem, der sie verwendet, das Versprechen geben würde, dass nichts ihren Wert ändern würde. Eine Routine, die eine IImmutableFoo akzeptiert, kann sie wie eine IReadableFoo verwenden, die Routine würde jedoch nur Klassen akzeptieren, die als implementierende IImmutableFoo deklariert wurden.

Ich kann mir nicht viele Anwendungsmöglichkeiten für "reine" Markerschnittstellen vorstellen. Ich kann mir nur vorstellen, wenn EqualityComparer (of T) .Default Object.Equals für jeden Typ zurückgeben würde, der IDoNotUseEqualityComparer implementiert hat, selbst wenn der Typ auch IEqualityComparer implementiert hat. Dies würde es einem ermöglichen, einen nicht abgedichteten unveränderlichen Typ zu haben, ohne gegen das Liskov-Substitutionsprinzip zu verstoßen: Wenn der Typ alle mit Gleichheitstests verbundenen Methoden versiegelt, könnte ein abgeleiteter Typ zusätzliche Felder hinzufügen und diese veränderlich machen, aber die Mutation solcher Felder würde nicht ' Sie sind mit allen Basismethoden sichtbar. Es ist möglicherweise nicht schrecklich, über eine nicht versiegelte unveränderliche Klasse zu verfügen und entweder die Verwendung von EqualityComparer.Default zu vermeiden oder abgeleiteten Klassen zu vertrauen, um IEqualityComparer nicht zu implementieren, aber eine abgeleitete Klasse, die IEqualityComparer implementierte, könnte als veränderliche Klasse erscheinen, selbst wenn sie als Basisklasse angesehen wird. Klassenobjekt.

4
supercat

Die Markerschnittstelle ist eigentlich nur eine prozedurale Programmierung in einer OO - Sprache. Eine Schnittstelle definiert einen Vertrag zwischen Implementierern und Konsumenten mit Ausnahme einer Markerschnittstelle, da eine Markerschnittstelle nichts außer sich definiert. Direkt am Tor versagt also das Marker-Interface am grundlegenden Zweck, ein Interface zu sein.

1
Ali Bayat

Die Markierungsschnittstelle ist eine leere Schnittstelle, die keine Rumpf-/Datenmitglieder/Implementierung hat.
Eine Klasse implementiert die Markierungsschnittstelle, wenn es erforderlich ist, es handelt sich lediglich um " mark "; bedeutet, dass der JVM mitgeteilt wird, dass die jeweilige Klasse zum Klonen dient, damit sie geklont werden kann. Diese bestimmte Klasse dient zur Serialisierung ihrer Objekte, also lassen Sie zu, dass ihre Objekte serialisiert werden.

0
Arun Raaj

Marker sind leere Schnittstellen. Eine Markierung ist entweder da oder nicht. 

klasse Foo: IConfidential

Hier kennzeichnen wir Foo als vertraulich. Es sind keine zusätzlichen zusätzlichen Eigenschaften oder Attribute erforderlich. 

0
Rick O'Shea