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?


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.


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.


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

"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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.