Ist die Verwendung von Schnittstellen für Datentypen ein Anti-Pattern?

9

Angenommen, ich habe verschiedene Entitäten in meinem Modell (mit EF), z. B. Benutzer, Produkt, Rechnung und Bestellung.

Ich schreibe ein Benutzersteuerelement, das die Zusammenfassungen von Entitätsobjekten in meiner Anwendung drucken kann, in denen die Entitäten zu einer vorab festgelegten Menge gehören. In diesem Fall sage ich, dass Zusammenfassungen von Benutzer und Produkt zusammengefasst werden können.

Die Zusammenfassungen haben alle nur eine ID und eine Beschreibung, daher erstelle ich eine einfache Schnittstelle dafür:

 public interface ISummarizableEntity {     
       public string ID { get; }    
       public string Description { get; } 
 }

Dann erstelle ich für die fraglichen Entitäten eine Teilklasse, die diese Schnittstelle implementiert:

public partial class User : ISummarizableEntity
{
    public string ID
    {
        get{ return UserID.ToString(); }
    }

    public string Description 
    {
        get{ return String.Format("{0} {1} is from {2} and is {3} years old", FirstName, LastName, Country, Age); }
    }
}

public partial class Product: ISummarizableEntity
{
    public string ID
    {
        get{ return ProductID.ToString(); }
    }

    public string Description 
    {
        get{ return String.Format("{0} weighs {1}{2} and belongs in the {3} department", ProductName, WeightValue, WeightUnit, Department); }
    }
}

Auf diese Weise kann meine Benutzersteuerung / Teilansicht nur an eine Sammlung von ISummarizableEntity gebunden werden und muss überhaupt nicht an der Quelle interessiert sein. Mir wurde gesagt, dass Schnittstellen nicht als Datentypen verwendet werden sollten, aber ich habe nicht mehr Informationen erhalten. Soweit ich sehen kann, obwohl Schnittstellen normalerweise das Verhalten beschreiben, ist die bloße Verwendung von Eigenschaften an sich kein Anti-Pattern, da Eigenschaften ohnehin nur syntaktischer Zucker für Getter / Setter sind.

Ich könnte einen konkreten Datentyp erstellen und diesen die Entitäten zuordnen, aber ich kann den Nutzen nicht erkennen. Ich könnte die Entitätsobjekte von einer abstrakten Klasse erben lassen und dann die Eigenschaften definieren, aber dann sperre ich die Entitäten für keine weitere Verwendung, da wir keine Mehrfachvererbung haben können. Ich bin auch offen dafür, dass jedes Objekt ISummarizableEntity ist, wenn ich wollte (natürlich würde ich die Schnittstelle umbenennen).

Die Lösung, die ich in meinem Kopf verwende, ist wartbar, erweiterbar, testbar und ziemlich robust. Kannst du das Anti-Muster hier sehen?

Sünde
quelle
Gibt es einen Grund, warum Sie dies vorziehen, wenn Sie so etwas haben EntitySummary, Userund Productjeder eine Methode hat public EntitySummary GetSummary()?
Ben Aaronson
@ Ben, Sie schlagen eine gültige Option vor. Es würde jedoch weiterhin die Definition einer Schnittstelle erfordern, die den Aufrufer darüber informiert, dass er von einem Objekt eine GetSummary () -Methode erwarten kann. Es ist im Wesentlichen das gleiche Design mit einem zusätzlichen Maß an Modularität in der Implementierung. Vielleicht sogar eine gute Idee, wenn die Zusammenfassung für sich allein (jedoch kurz) getrennt von ihrer Quelle leben muss.
Kent A.
@ KentAnderson Ich stimme zu. Abhängig von der Gesamtschnittstelle dieser Klassen und der Verwendung von Zusammenfassungen kann dies eine gute Idee sein oder auch nicht.
Ben Aaronson

Antworten:

17

Schnittstellen beschreiben kein Verhalten. Im Gegenteil, manchmal.

Schnittstellen beschreiben Verträge, z. B. "Wenn ich dieses Objekt einer Methode anbieten möchte, die eine ISummarizableEntity akzeptiert, muss dieses Objekt eine Entität sein, die sich selbst zusammenfassen kann" - in Ihrem Fall wird dies als Rückgabe von a definiert Zeichenfolgen-ID und eine Zeichenfolgenbeschreibung.

Das ist eine perfekte Verwendung von Schnittstellen. Kein Anti-Muster hier.

pdr
quelle
2
"Schnittstellen beschreiben kein Verhalten." Wie ist "sich zusammenfassen" kein Verhalten?
Doval
2
@ThomasStringer, Vererbung impliziert aus puristischer Sicht OO eine gemeinsame Abstammung (z. B. sind ein Quadrat und ein Kreis beide Formen ). Im Beispiel von OP haben ein Benutzer und ein Produkt keine vernünftige gemeinsame Abstammung. Vererbung wäre in diesem Fall ein klares Anti-Muster.
Kent A.
2
@Doval: Ich denke, der Name der Schnittstelle kann das erwartete Verhalten beschreiben. Aber es muss nicht; Die Schnittstelle könnte gleichermaßen IHasIdAndDescription heißen und die Antwort wäre dieselbe. Die Schnittstelle selbst beschreibt kein Verhalten, sondern Erwartungen.
pdr
2
@pdr Wenn Sie 20 V über eine Kopfhörerbuchse senden, passieren schlimme Dinge. Die Form ist nicht genug; Es gibt eine sehr reale und sehr wichtige Erwartung, welche Art von Signal durch diesen Stecker kommen wird. Genau aus diesem Grund ist es falsch, vorzutäuschen, dass an Schnittstellen keine Verhaltensspezifikationen angehängt sind. Was können Sie mit Listeiner Liste tun , die sich nicht wie eine Liste verhält?
Doval
3
Ein elektrischer Stecker mit der entsprechenden Schnittstelle passt möglicherweise in die Steckdose, dies bedeutet jedoch nicht, dass er Strom leitet (das gewünschte Verhalten).
JeffO
5

Sie haben den besseren Pfad für dieses Design gewählt, weil Sie einen bestimmten Verhaltenstyp definieren, der für mehrere unterschiedliche Objekttypen erforderlich ist. Vererbung würde in diesem Fall eine gemeinsame Beziehung zwischen den Klassen implizieren, die tatsächlich nicht existiert. In diesem Fall wird die Kompositionsfähigkeit der Vererbung vorgezogen.

Kent A.
quelle
3
Schnittstellen haben nichts mit Vererbung zu tun.
DougM
1
@DougM, vielleicht habe ich es nicht gut gesagt, aber ich bin mir ziemlich sicher, dass wir uns einig sind.
Kent A.
1

Schnittstellen, die nur Eigenschaften tragen, sollten vermieden werden, da:

  • es verschleiert die Absicht: Sie benötigen lediglich einen Datencontainer
  • es fördert die Vererbung: Wahrscheinlichkeit, dass jemand in Zukunft Bedenken vermischt
  • Es verhindert die Serialisierung

Hier mischen Sie zwei Bedenken:

  • Zusammenfassung als Daten
  • Zusammenfassung als Vertrag

Eine Zusammenfassung besteht aus zwei Zeichenfolgen: einer ID und einer Beschreibung. Dies sind einfache Daten:

public class Summary {
    private readonly string id;
    private readonly string description;
    public Summary(string id, string description) {
        this.id = id;
        this.description = description;
    }
    public string Id { get { return id; } }
    public string Description { get { return description; } }
}

Nachdem Sie definiert haben, was eine Zusammenfassung ist, möchten Sie einen Vertrag definieren:

public interface ISummarizableEntity {
    public Summary GenerateSummary();
}

Beachten Sie, dass die Verwendung von Intelligenz in Gettern ein Anti-Muster ist und vermieden werden sollte: Sie sollte sich stattdessen in Funktionen befinden. So sehen Implementierungen aus:

public partial class User : ISummarizableEntity {
    public Summary GenerateSummary() {
        var id = UserID.ToString();
        var description = String.Format("{0} {1} is from {2} and is {3} years old", FirstName, LastName, Country, Age);
        return new Summary(id,description);
    }
}

public partial class Product : ISummarizableEntity {
    public Summary GenerateSummary() {
        var id = ProductID.ToString();
        var description = String.Format("{0} weighs {1}{2} and belongs in the {3} department", ProductName, WeightValue, WeightUnit, Department);
        return new Summary(id,description);
    }
}
vanna
quelle
"Schnittstellen, die nur Eigenschaften tragen, sollten vermieden werden", stimme ich nicht zu. Begründen Sie, warum Sie so denken.
Euphoric
Sie haben Recht, ich habe einige Details hinzugefügt
vanna