L'utilisation d'interfaces pour les types de données est-elle un anti-modèle?

9

Supposons que j'ai plusieurs entités dans mon modèle (en utilisant EF), par exemple Utilisateur, Produit, Facture et Commande.

J'écris un contrôle utilisateur qui peut imprimer les résumés des objets entité dans mon application où les entités appartiennent à un ensemble prédéterminé, dans ce cas, je dis que les résumés utilisateur et produit peuvent être résumés.

Les résumés n'auront tous qu'un ID et une description, donc je crée une interface simple pour cela:

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

Ensuite, pour les entités en question, je crée une classe partielle qui implémente cette interface:

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); }
    }
}

De cette façon, mon contrôle utilisateur / vue partielle peut simplement se lier à n'importe quelle collection de ISummarizableEntity et n'a pas besoin d'être intéressé par la source. On m'a dit que les interfaces ne devraient pas être utilisées comme types de données, mais je n'ai pas obtenu plus d'informations que cela. Pour autant que je puisse voir, bien que les interfaces décrivent normalement le comportement, le simple fait d'utiliser des propriétés n'est pas un anti-modèle en soi, car les propriétés ne sont que du sucre syntaxique pour les getters / setters de toute façon.

Je pourrais créer un type de données concret et une carte des entités à cela, mais je ne vois pas l'avantage. Je pourrais faire en sorte que les objets entité héritent d'une classe abstraite, puis définir les propriétés, mais je verrouille ensuite les entités pour qu'elles ne soient plus utilisées car nous ne pouvons pas avoir d'héritage multiple. Je suis également ouvert à ce que tout objet soit ISummarizableEntity si je le voulais (évidemment, je renommerais l'interface)

La solution que j'utilise dans mon esprit est maintenable, extensible, testable et assez robuste. Pouvez-vous voir l'anti-modèle ici?

Péché
la source
Y a-t-il une raison pour laquelle vous préférez cela plutôt que d'avoir quelque chose comme EntitySummary, avec Useret Productchacun ayant une méthode comme public EntitySummary GetSummary()?
Ben Aaronson
@Ben, vous proposez une option valide. Mais cela nécessiterait toujours la définition d'une interface permettant à l'appelant de savoir qu'il peut s'attendre à ce qu'un objet ait une méthode GetSummary (). Il s'agit essentiellement de la même conception avec un niveau de modularité supplémentaire dans la mise en œuvre. Peut-être même une bonne idée si le résumé doit vivre seul (même brièvement) indépendamment de sa source.
Kent A.
@KentAnderson, je suis d'accord. Cela peut ou non être une bonne idée, selon l'interface globale de ces classes et la façon dont les résumés sont utilisés.
Ben Aaronson

Réponses:

17

Les interfaces ne décrivent pas le comportement. Bien au contraire, parfois.

Les interfaces décrivent des contrats, tels que "si je dois proposer cet objet à une méthode qui accepte une entité ISummarizable, cet objet doit être une entité capable de se résumer" - dans votre cas, cela est défini comme étant capable de renvoyer un ID de chaîne et description de chaîne.

C'est une utilisation parfaite des interfaces. Pas d'anti-motif ici.

pdr
la source
2
"Les interfaces ne décrivent pas le comportement." Comment "se résumer" n'est-il pas un comportement?
Doval
2
@ThomasStringer, héritage, d'une vue puriste OO, implique une ascendance commune (par exemple, un carré et un cercle sont les deux formes ). Dans l'exemple d'OP, un utilisateur et un produit ne partagent aucune ascendance commune raisonnable. L'hérédité dans ce cas serait un anti-modèle clair.
Kent A.
2
@Doval: Je suppose que le nom de l'interface peut décrire le comportement attendu. Mais ce n'est pas nécessaire; l'interface pourrait également être nommée IHasIdAndDescription et la réponse serait la même. L'interface elle-même ne décrit pas le comportement, elle décrit les attentes.
pdr
2
@pdr Si vous envoyez 20V via une prise casque, de mauvaises choses se produiront. La forme ne suffit pas; il y a une attente très réelle et très importante quant au type de signal qui va passer par cette prise. C'est précisément pourquoi il est faux de prétendre que les interfaces n'ont pas de spécifications de comportement. Que pouvez - vous faire avec un Listqui ne se comporte pas comme une liste?
Doval
3
Une prise électrique avec l'interface appropriée peut s'insérer dans la prise, mais cela ne signifie pas qu'elle conduira l'électricité (le comportement souhaité).
JeffO
5

Vous avez choisi le meilleur chemin pour cette conception car vous définissez un type de comportement spécifique qui sera requis pour plusieurs types d'objets différents. L'hérédité dans ce cas impliquerait une relation commune entre les classes qui n'existe pas réellement. Dans ce cas, la composabilité est préférée à l'héritage.

Kent A.
la source
3
Les interfaces n'ont rien à voir avec l'héritage.
DougM
1
@DougM, je ne l'ai peut-être pas bien dit, mais je suis sûr que nous sommes d'accord.
Kent A.
1

Les interfaces qui ne portent que des propriétés doivent être évitées car:

  • il obscurcit l'intention: vous avez uniquement besoin d'un conteneur de données
  • il encourage l'héritage: probabilité que quelqu'un mélange ses préoccupations à l'avenir
  • il empêche la sérialisation

Ici, vous mélangez deux préoccupations:

  • résumé en tant que données
  • résumé en tant que contrat

Un résumé est composé de deux chaînes: un identifiant et une description. Ce sont des données simples:

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; } }
}

Maintenant que vous avez défini ce qu'est un résumé, vous voulez définir un contrat:

public interface ISummarizableEntity {
    public Summary GenerateSummary();
}

Notez que l'utilisation de l'intelligence dans les getters est un anti-modèle et doit être évitée: elle doit plutôt être localisée dans les fonctions. Voici à quoi ressemblent les implémentations:

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
la source
"Les interfaces qui ne portent que des propriétés doivent être évitées", je ne suis pas d'accord. Expliquez pourquoi pensez-vous que oui.
Euphoric
Vous avez raison, j'ai ajouté quelques détails
vanna