O uso de interfaces para tipos de dados é um antipadrão?

9

Suponha que eu tenha várias entidades no meu modelo (usando EF), como Usuário, Produto, Fatura e Pedido.

Estou escrevendo um controle de usuário que pode imprimir os resumos dos objetos de entidade no meu aplicativo, onde as entidades pertencem a um conjunto pré-decidido; nesse caso, digo que os resumos de Usuário e Produto podem ser resumidos.

Todos os resumos terão apenas um ID e uma descrição, por isso crio uma interface simples para isso:

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

Em seguida, para as entidades em questão, crio uma classe parcial que implementa essa 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); }
    }
}

Dessa forma, meu controle de usuário / visão parcial pode ser vinculado a qualquer coleção de ISummarizableEntity e não precisa estar interessado na fonte. Foi-me dito que as interfaces não deveriam ser usadas como tipos de dados, mas não obtive mais informações do que isso. Até onde eu posso ver, embora as interfaces normalmente descrevam o comportamento, apenas o uso de propriedades não é um antipadrão, já que propriedades são apenas açúcar sintático para getters / setters de qualquer maneira.

Eu poderia criar um tipo de dados e um mapa concretos das entidades para isso, mas não vejo o benefício. Eu poderia fazer com que os objetos de entidade fossem herdados de uma classe abstrata e depois definir as propriedades, mas estou bloqueando as entidades para nenhum uso adicional, pois não podemos ter herança múltipla. Também estou aberto a ter qualquer objeto ISummarizableEntity se eu quiser (obviamente, renomeio a interface)

A solução que estou usando em minha mente é sustentável, extensível, testável e bastante robusta. Você pode ver o anti-padrão aqui?

Pecado
fonte
Existe alguma razão para você preferir ter algo como EntitySummary, com Usere Productcada um com um método como public EntitySummary GetSummary()?
precisa
@ Ben, você sugere uma opção válida. Mas ainda assim exigiria a definição de uma interface que permita ao chamador saber que pode esperar que um objeto tenha um método GetSummary (). É essencialmente o mesmo design, com um nível adicional de modularidade na implementação. Talvez seja uma boa idéia se o resumo precisar viver por conta própria (embora brevemente), separado da fonte.
Kent A.
@KentAnderson Eu concordo. Pode ou não ser realmente uma boa ideia, dependendo da interface geral dessas classes e de como os resumos são usados.
precisa

Respostas:

17

Interfaces não descrevem comportamento. Muito pelo contrário, às vezes.

As interfaces descrevem contratos, como "se eu devo oferecer esse objeto a qualquer método que aceite ISummarizableEntity, esse objeto deve ser uma entidade capaz de se resumir" - no seu caso, definido como sendo capaz de retornar um ID da string e Descrição da string.

Esse é um uso perfeito de interfaces. Nenhum anti-padrão aqui.

pdr
fonte
2
"Interfaces não descrevem comportamento." Como "resumir-se" não é um comportamento?
Doval
2
@ThomasStringer, herança, de uma visão purista de OO, implica uma ancestralidade comum (por exemplo, um quadrado e um círculo são formas ). No exemplo do OP, um Usuário e um Produto não compartilham nenhuma ancestralidade comum razoável. A herança neste caso seria um claro anti-padrão.
Kent A.
2
@Doval: Eu acho que o nome da interface pode descrever o comportamento esperado. Mas não precisa; a interface poderia igualmente ter o nome IHasIdAndDescription e a resposta seria a mesma. A interface em si não descreve o comportamento, descreve as expectativas.
Pdr4
2
@pdr Se você enviar 20V através de um fone de ouvido, coisas ruins acontecerão. A forma não é suficiente; há uma expectativa muito real e muito importante de que tipo de sinal vai passar por esse plug. É exatamente por isso que fingir que as interfaces não têm especificações de comportamento anexadas a elas está errado. O que você pode fazer com um Listque não se comporte como uma lista?
Doval
3
Um plugue elétrico com a interface apropriada pode caber na tomada, mas isso não significa que ele conduz eletricidade (o comportamento desejado).
Jeffo
5

Você escolheu o melhor caminho para esse design porque está definindo um tipo específico de comportamento que será necessário para vários tipos diferentes de objetos. A herança nesse caso implicaria um relacionamento comum entre as classes que não existe realmente. Nesse caso, a composibilidade é preferida à herança.

Kent A.
fonte
3
As interfaces não têm nada a ver com herança.
DougM
11
@ DougM, talvez eu não tenha dito bem, mas tenho certeza de que concordamos.
Kent A.
1

As interfaces que carregam apenas propriedades devem ser evitadas, pois:

  • ofusca a intenção: você só precisa de um contêiner de dados
  • incentiva a herança: probabilidade de que alguém misture preocupações no futuro
  • impede serialização

Aqui você está misturando duas preocupações:

  • resumo como um dado
  • resumo como contrato

Um resumo é composto de duas cadeias: um ID e uma descrição. Estes são dados 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; } }
}

Agora que você definiu o resumo, deseja definir um contrato:

public interface ISummarizableEntity {
    public Summary GenerateSummary();
}

Observe que o uso de inteligência em getters é um antipadrão e deve ser evitado: ele deve estar localizado em funções. Aqui está como as implementações se parecem:

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
fonte
"Interfaces que apenas carregam propriedades devem ser evitadas" Eu discordo. Forneça raciocínio por que você pensa assim.
eufórico
Você está certo, eu adicionei alguns detalhes
vanna