¿Usar interfaces para tipos de datos es un antipatrón?

9

Supongamos que tengo varias entidades en mi modelo (usando EF), como Usuario, Producto, Factura y Pedido.

Estoy escribiendo un control de usuario que puede imprimir los resúmenes de objetos de entidad en mi aplicación donde las entidades pertenecen a un conjunto predeterminado, en este caso digo que se pueden resumir resúmenes de Usuario y Producto.

Todos los resúmenes solo tendrán un ID y una descripción, así que creo una interfaz simple para esto:

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

Luego, para las entidades en cuestión, creo una clase parcial que implementa esta interfaz:

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 esta manera, mi control de usuario / vista parcial puede unirse a cualquier colección de ISummarizableEntity y no necesita estar interesado en la fuente. Me han dicho que las interfaces no deben usarse como tipos de datos, pero no obtuve más información que eso. Por lo que puedo ver, aunque las interfaces normalmente describen el comportamiento, el solo uso de propiedades no es un antipatrón en sí mismo, ya que las propiedades son solo azúcar sintáctica para captadores / establecedores de todos modos.

Podría crear un tipo de datos concreto y un mapa de las entidades a eso, pero no puedo ver el beneficio. Podría hacer que los objetos de entidad hereden de una clase abstracta y luego definir las propiedades, pero luego estoy bloqueando las entidades para que no se usen más, ya que no podemos tener herencia múltiple. También estoy dispuesto a que cualquier objeto sea ISummarizableEntity si quisiera (obviamente cambiaría el nombre de la interfaz)

La solución que estoy usando en mi mente es mantenible, extensible, comprobable y bastante robusta. ¿Puedes ver el antipatrón aquí?

Pecado
fuente
¿Hay alguna razón por la que prefiera esto en lugar de tener algo como EntitySummary, Usery Productcada uno con un método como public EntitySummary GetSummary()?
Ben Aaronson
@Ben, sugieres una opción válida. Pero aún requeriría la definición de una interfaz que le permita al llamante saber que puede esperar que un objeto tenga un método GetSummary (). Es esencialmente el mismo diseño con un nivel adicional de modularidad en la implementación. Tal vez incluso una buena idea si el resumen necesita vivir solo (aunque sea brevemente) separado de su fuente.
Kent A.
@KentAnderson Estoy de acuerdo. Puede o no ser una buena idea, dependiendo de la interfaz general de estas clases y de cómo se usan los resúmenes.
Ben Aaronson

Respuestas:

17

Las interfaces no describen el comportamiento. Todo lo contrario, a veces.

Las interfaces describen contratos, como "si voy a ofrecer este objeto a cualquier método que acepte una entidad ISummarizableEntity, este objeto debe ser una entidad que pueda resumirse a sí misma", en su caso, que se define como capaz de devolver un ID de cadena y una descripción de cadena.

Ese es un uso perfecto de las interfaces. No hay antipatrón aquí.

pdr
fuente
2
"Las interfaces no describen el comportamiento". ¿Cómo es "resumirse a sí mismo" no un comportamiento?
Doval
2
@ThomasStringer, la herencia, desde un punto de vista purista OO, implica una ascendencia común (por ejemplo, un cuadrado y un círculo son ambas formas ). En el ejemplo de OP, un Usuario y un Producto no comparten ascendencia común razonable. La herencia en este caso sería un claro antipatrón.
Kent A.
2
@Doval: Supongo que el nombre de la interfaz puede describir el comportamiento esperado. Pero no tiene que hacerlo; la interfaz también podría llamarse IHasIdAndDescription y la respuesta sería la misma. La interfaz en sí no describe el comportamiento, describe las expectativas.
pdr
2
@pdr Si envía 20V a través de un conector para auriculares, sucederán cosas malas. La forma no es suficiente; Hay una expectativa muy real y muy importante de qué tipo de señal va a pasar a través de ese enchufe. Esta es precisamente la razón por la cual es incorrecto fingir que las interfaces no tienen especificaciones de comportamiento asociadas. ¿Qué puedes hacer con un Listque no se comporta como una lista?
Doval
3
Un enchufe eléctrico con la interfaz adecuada puede caber en la toma de corriente, pero eso no significa que conducirá electricidad (el comportamiento deseado).
JeffO
5

Ha elegido la mejor ruta para este diseño porque está definiendo un tipo específico de comportamiento que se requerirá de varios tipos de objetos diferentes. La herencia en este caso implicaría una relación común entre las clases que en realidad no existe. En este caso, la componibilidad se ve favorecida sobre la herencia.

Kent A.
fuente
3
Las interfaces no tienen nada que ver con la herencia.
DougM
1
@DougM, tal vez no lo dije bien, pero estoy bastante seguro de que estamos de acuerdo.
Kent A.
1

Las interfaces que solo tienen propiedades deben evitarse ya que:

  • ofusca la intención: solo necesita un contenedor de datos
  • fomenta la herencia: probabilidad de que alguien mezcle preocupaciones en el futuro
  • evita la serialización

Aquí estás mezclando dos preocupaciones:

  • resumen como datos
  • resumen como un contrato

Se hace un resumen de dos cadenas: una identificación y una descripción. Estos son datos 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; } }
}

Ahora que ha definido qué es un resumen, desea definir un contrato:

public interface ISummarizableEntity {
    public Summary GenerateSummary();
}

Tenga en cuenta que el uso de inteligencia en getters es un antipatrón y debe evitarse: en su lugar, debe ubicarse en funciones. Así es como se ven las implementaciones:

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
fuente
"Las interfaces que solo tienen propiedades deben evitarse" No estoy de acuerdo. Proporcione razonamiento por qué lo cree así.
Eufórico
Tienes razón, agregué algunos detalles
vanna