Ist es eine schlechte Praxis, eine Schnittstelle nur für die Kategorisierung zu verwenden?

12

Beispielsweise:

Sagen wir , ich Klassen A, B, C. Ich habe zwei Schnittstellen, nennen wir sie IAnimalund IDog. IDogerbt von IAnimal. Aund Bsind IDogs, während Ces nicht ist, aber es ist ein IAnimal.

Der wichtige Teil ist, dass es IDogkeine zusätzlichen Funktionen gibt. Es wird nur verwendet, um bestimmte Methoden als Argument zuzulassen Aund Bnicht Czu übergeben.

Ist das eine schlechte Praxis?

Kendall Frey
quelle
8
IAnimalund IDogsind schreckliche Tautologienamen!
5
@JarrodRoberson Ich stimme zwar eher zu, aber wenn Sie in der .Net-Welt arbeiten, bleiben Sie irgendwie dabei (oder werden gegen die etablierte Konvention verstoßen, wenn Sie es anders machen).
Daniel B
2
@ JarrodRoberson IMHO MyProject.Data.IAnimalund MyProject.Data.Animalsind besser als MyProject.Data.Interfaces.Animalund MyProject.Data.Implementations.Animal
Mike Koder
1
Der Punkt ist, dass es keinen Grund geben sollte, sich auch nur darum zu kümmern, ob es sich um ein Interfaceoder Implementation, ob in einem repetitiven Präfix oder einem Namespace, um eine Tautologie handelt und gegen DRY verstößt. Dogist alles, was Sie interessieren sollte. PitBull extends Dogbenötigt auch keine Implementierungsredundanz, das Wort extendssagt mir alles, was ich wissen muss, lies den Link, den ich in meinem ursprünglichen Kommentar gepostet habe.
1
@ Jarrod: Hör auf, dich bei mir zu beschweren. Beschweren Sie sich beim .NET-Entwicklerteam. Wenn ich gegen den Standard verstoßen würde, würden meine Kollegen meinen Bauch hassen.
Kendall Frey

Antworten:

13

Schnittstelle, die kein Mitglied hat oder genau dieselben Mitglieder hat, mit einer anderen Schnittstelle, die von derselben Schnittstelle namens Marker Interface geerbt wird und die Sie als Marker verwenden.

Es ist keine schlechte Praxis, aber die Benutzeroberfläche kann durch Attribute (Anmerkungen) ersetzt werden, wenn die von Ihnen verwendete Sprache dies unterstützt.

Ich kann zuversichtlich sagen, dass es keine schlechte Praxis ist, da ich in Orchard Project ein stark genutztes "Marker Interface" -Muster gesehen habe

Hier ist ein Beispiel aus dem Orchard-Projekt.

public interface ISingletonDependency : IDependency {}

/// <summary>
/// Base interface for services that may *only* be instantiated in a unit of work.
/// This interface is used to guarantee they are not accidentally referenced by a singleton dependency.
/// </summary>
public interface IUnitOfWorkDependency : IDependency {}

/// <summary>
/// Base interface for services that are instantiated per usage.
/// </summary>
public interface ITransientDependency : IDependency {}

Bitte beziehen Sie sich auf Marker Interface .

Frisches Blut
quelle
Würden Attribute / Annotationen die Überprüfung der Kompilierungszeit unterstützen (mit C # als Referenzsprache)? Ich weiß, dass ich eine Ausnahme auslösen könnte, wenn das Objekt das Attribut nicht besitzt, das Schnittstellenmuster jedoch beim Kompilieren Fehler abfängt.
Kendall Frey
Wenn Sie Marker Interface verwenden, haben Sie also überhaupt keinen Vorteil bei der Prüfung der Kompilierung. Grundsätzlich führen Sie eine Typprüfung über die Schnittstelle durch.
Freshblood
Ich bin verwirrt. Dieses 'Marker-Interface'-Muster bietet Überprüfungen zur Kompilierungszeit, da Sie ein nicht Can eine Methode übergeben können, die ein erwartet IDog.
Kendall Frey
Wenn Sie dieses Muster verwenden, erledigen Sie in hohem Maße Reflexionsaufgaben. Ich meine, Sie sind dynamisch oder statisch und führen Typprüfungen durch. Ich bin sicher, Sie haben etwas falsch in Ihrem Anwendungsfall, aber ich habe nicht gesehen, wie Sie es verwenden.
Freshblood
Lassen Sie es mich so beschreiben, wie ich es im Kommentar zu Telastyns Antwort getan habe. zB habe ich eine KennelKlasse, die eine Liste von IDogs speichert .
Kendall Frey,
4

Ja, es ist eine schlechte Praxis in fast jeder Sprache.

Typen sind nicht dazu da, Daten zu kodieren. Sie sind ein Hilfsmittel, um zu beweisen, dass Ihre Daten dahin gelangen, wo sie benötigt werden, und sich so verhalten, wie sie sich verhalten müssen. Der einzige Grund für die Untertypisierung besteht darin, dass sich ein polymorphes Verhalten ändert (und nicht immer dann).

Da nicht getippt wird, wird vorausgesetzt, was ein IDog kann. Ein Programmierer denkt eine Sache, ein anderer denkt eine andere und die Dinge brechen zusammen. Oder die Dinge, die es darstellt, nehmen zu, wenn Sie eine feinkörnigere Steuerung benötigen.

[Bearbeiten: Klarstellung]

Ich meine, Sie machen eine Logik, die auf der Schnittstelle basiert. Warum funktionieren manche Methoden nur bei Hunden und andere bei Tieren? Diese Differenz ist ein impliziter Vertrag, da es kein tatsächliches Mitglied gibt, das die Differenz liefert. Keine aktuellen Daten im System. Der einzige Unterschied ist die Benutzeroberfläche (daher der Kommentar ohne Eingabe) und was dies bedeutet, wird sich zwischen den Programmierern unterscheiden. [/bearbeiten]

Bestimmte Sprachen bieten Charakterzugsmechanismen, bei denen dies nicht so schlimm ist, aber bei denen der Schwerpunkt eher auf Fähigkeiten als auf Verfeinerungen liegt. Ich habe wenig Erfahrung mit ihnen und kann daher nicht mit den dortigen Best Practices sprechen. In C ++ / Java / C # allerdings ... nicht gut.

Telastyn
quelle
Ihre beiden mittleren Absätze verwirren mich. Was meinst du mit "Untertyp"? Ich verwende Schnittstellen, bei denen es sich um Verträge handelt, nicht um Implementierungen, und ich bin nicht sicher, wie Ihre Kommentare zutreffen. Was meinst du mit "kein Tippen"? Was meinst du mit "impliziert"? Ein IDog kann alles, was ein IAnimal kann, aber es kann nicht garantiert werden, dass er mehr kann.
Kendall Frey
Danke für die Klarstellung. In meinem speziellen Fall benutze ich die Schnittstellen nicht genau anders. Es lässt sich wahrscheinlich am besten beschreiben, wenn ich sage, dass ich eine KennelKlasse habe, in der eine Liste von IDogs gespeichert ist.
Kendall Frey,
@KendallFrey: Entweder wirfst du die Schnittstelle aus (schlecht), oder du erstellst eine künstliche Unterscheidung, die nach einer falschen Abstraktion riecht.
Telastyn
2
@ Telastyn - Zweck einer solchen Schnittstelle ist die Bereitstellung von Metadaten
Freshblood
1
@Telastyn: Wie wirken sich Attribute auf die Überprüfung der Kompilierungszeit aus? In C # können AFAIK-Attribute nur zur Laufzeit verwendet werden.
Kendall Frey,
2

Ich kenne C # nur gut, daher kann ich das für die OO-Programmierung im Allgemeinen nicht sagen, aber als C # -Beispiel, wenn Sie Klassen kategorisieren müssen, sind die offensichtlichen Optionen Schnittstellen, Enum-Eigenschaften oder benutzerdefinierte Attribute. Normalerweise würde ich die Schnittstelle wählen, egal was andere denken.

if(animal is IDog)
{
}
if(animal.Type == AnimalType.Dog)
{
}
if(animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
{
}


var dogs1 = animals.OfType<IDog>();
var dogs2 = animals.Where(a => a.Type == AnimalType.Dog);
var dogs3 = animals.Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any());


var dogsFromDb1 = session.Query<IDog>();
var dogsFromDb2 = session.Query<Animal>().Where(a => a.Type == AnimalType.Dog);
var dogsFromDb3 = session.Query<Animal>().Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute),false).Any());


public void DoSomething(IDog dog)
{
}
public void DoSomething(Animal animal)
{
    if(animal.Type != AnimalType.Dog)
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}
public void DoSomething(Animal animal)
{
    if(!animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}
Mike Koder
quelle
Der letzte Codeabschnitt ist hauptsächlich das, wofür ich ihn benutze. Ich kann sehen, dass die Schnittstellenversion offensichtlich kürzer ist und dass die Kompilierungszeit überprüft wird.
Kendall Frey
Ich habe einen ähnlichen Anwendungsfall, aber die meisten .net-Entwickler raten dringend davon ab, aber ich werde keine Attribute verwenden
GorillaApe
-1

Es ist nichts Falsches daran, eine Schnittstelle zu haben, die eine andere Schnittstelle erbt, ohne neue Mitglieder hinzuzufügen, wenn erwartet wird, dass alle Implementierungen der letzteren Schnittstelle in der Lage sind, nützliche Dinge zu tun, die einige Implementierungen der ersteren möglicherweise nicht können. In der Tat ist es ein gutes Muster, das IMHO eher in Dingen wie dem .net Framework hätte verwenden sollen, zumal ein Implementierer, dessen Klasse natürlich die von der stärker abgeleiteten Schnittstelle implizierten Einschränkungen erfüllen würde, dies einfach durch Implementieren der stärker abgeleiteten Schnittstelle anzeigen kann , ohne den Implementierungscode oder die Deklarationen der Mitglieder ändern zu müssen.

Angenommen, ich habe die folgenden Schnittstellen:

Schnittstelle IMaybeMutableFoo {
  Bar Thing {get; set;} // und auch andere Eigenschaften
  bool IsMutable;
  IImmutableFoo AsImmutable ();
}
Schnittstelle IImmutableFoo: IMaybeMutableFoo {};

Es wird IImmutableFoo.AsImmutable()erwartet, dass eine Implementierung von sich selbst zurückgibt. Von einer Implementierung einer veränderlichen Klasse wird IMaybeMutableFoo.AsImmutable()erwartet, dass sie ein neues, tief unveränderliches Objekt zurückgibt, das eine Momentaufnahme der Daten enthält. Eine Routine, die einen Schnappschuss der in einer gespeicherten Daten speichern möchte, kann zu IMaybeMutableFooÜberlastungen führen:

public void StoreData (IImmutableFoo ThingToStore)
{
  // Das Speichern der Referenz ist ausreichend, da bekannt ist, dass ThingToStore unveränderlich ist
}
public void StoreData (IMaybeMutableFoo ThingToStore)
{
  StoreData (ThingToStore.AsImmutable ()); // Rufe die spezifischere Überladung auf
}

In Fällen, in denen der Compiler nicht weiß, dass es sich bei dem zu speichernden Objekt um einen handelt IImmutableFoo, ruft er auf AsImmutable(). Die Funktion sollte schnell zurückkehren, wenn das Element bereits unveränderlich ist, ein Funktionsaufruf über eine Schnittstelle jedoch noch einige Zeit in Anspruch nimmt. Wenn der Compiler weiß, dass das Element bereits ein Element ist IImmutableFoo, kann er den unnötigen Funktionsaufruf überspringen.

Superkatze
quelle
Downvoter: Möchtest du einen Kommentar abgeben? Es gibt Zeiten, in denen eine Schnittstelle geerbt wird, ohne dass etwas Neues hinzugefügt wird. Dies bedeutet jedoch nicht, dass es keine Zeiten gibt, in denen dies eine gute (und meiner Meinung nach wenig genutzte) Technik ist.
Supercat