É uma má prática usar uma interface apenas para categorização?

12

Por exemplo:

Digamos que eu tenha aulas A, B, C. Eu tenho duas interfaces, vamos chamá-los IAnimale IDog. IDogherda de IAnimal. Ae Bsão IDogs, enquanto Cnão é, mas é um IAnimal.

A parte importante é que IDognão fornece funcionalidade adicional. É usado apenas para permitir Ae B, mas não C, ser passado como argumento para certos métodos.

Isso é uma prática ruim?

Kendall Frey
fonte
5
@JarrodRoberson, apesar de eu concordar que, se você trabalha no mundo da .net, está meio que preso a ela (ou vai contra a convenção estabelecida se o fizer de maneira diferente).
Daniel B
2
@JarrodRoberson IMHO MyProject.Data.IAnimale MyProject.Data.Animalsão melhores que MyProject.Data.Interfaces.Animale # MyProject.Data.Implementations.Animal
Mike Koder
1
A questão é que não deve haver nenhuma razão para se importar se é um Interfaceou Implementation, seja em um prefixo repetitivo ou em um espaço para nome, é uma tautologia de qualquer maneira e viola o DRY. Dogé tudo com o que você deve se preocupar. PitBull extends Dogtambém não precisa de redundância de implementação, a palavra extendsme diz tudo o que preciso saber, leia o link que publiquei no meu comentário original.
1
@ Jarrod: Pare de reclamar comigo. Reclame com a equipe de desenvolvimento .NET. Se eu fosse contra o padrão, meus colegas desenvolvedores odiariam minha coragem.
Kendall Frey

Respostas:

13

Interface que não possui nenhum membro ou tem exatamente os mesmos membros com outra interface herdada da mesma interface chamada Interface do Marcador e você a está usando como marcador.

Não é uma prática recomendada, mas a interface pode ser substituída por atributos (anotações) se o idioma que você estiver usando o suportar.

Posso dizer com confiança que não é uma prática ruim, porque eu vi o padrão "Marker Interface" de uso pesado no Orchard Project

Aqui está um exemplo do projeto Orchard.

public interface ISingletonDependency : IDependency {}

/// <summary>
/// Base interface for services that may *only* be instantiated in a unit of work.
/// This interface is used to guarantee they are not accidentally referenced by a singleton dependency.
/// </summary>
public interface IUnitOfWorkDependency : IDependency {}

/// <summary>
/// Base interface for services that are instantiated per usage.
/// </summary>
public interface ITransientDependency : IDependency {}

Por favor, consulte a Interface do Marcador .

Sangue fresco
fonte
Os atributos / anotações suportariam a verificação em tempo de compilação (usando C # como idioma de referência)? Eu sei que poderia lançar uma exceção se o objeto não tiver o atributo, mas o padrão da interface captura erros em tempo de compilação.
Kendall Frey
Se você estiver usando a Interface do Marcador, não terá o benefício da verificação de compilação. Você está basicamente fazendo verificação de tipo por interface.
Freshblood
Estou confuso. Esse padrão de 'interface do marcador' fornece verificações em tempo de compilação, pois você não pode passar um Cpara um método que espera um IDog.
Kendall Frey
Se você estiver usando esse padrão, estará realizando trabalhos reflexivos. Quero dizer, você está dinamicamente ou estaticamente fazendo verificações de tipo. Tenho certeza de que há algo errado no seu caso de uso, mas não vi como você o está usando.
Freshblood
Deixe-me descrever como fiz no comentário sobre a resposta de Telastyn. por exemplo, eu tenho uma Kennelclasse que armazena uma lista de IDogs.
Kendall Frey
4

Sim, é uma prática ruim em quase todos os idiomas.

Os tipos não existem para codificar dados; eles são uma ajuda para provar que seus dados vão para onde precisam e se comportam como precisam. A única razão para subtipo é se um comportamento polimórfico muda (e nem sempre então).

Como não há digitação, o que um IDog pode fazer está implícito. Um programador pensa uma coisa, outro pensa outra e as coisas quebram. Ou as coisas que ele representa aumentam à medida que você precisa de um controle mais refinado.

[editar: esclarecimentos]

O que quero dizer é que você está fazendo alguma lógica com base na interface. Por que alguns métodos só funcionam com cães e outros com qualquer animal? Essa diferença é um contrato implícito, pois não há um membro real que forneça a diferença; nenhum dado real no sistema. A única diferença é a interface (daí o comentário 'no digitando'), e o que isso significa será diferente entre os programadores. [/editar]

Certos idiomas fornecem mecanismos de características em que isso não é muito ruim, mas tendem a se concentrar nos recursos, não no refinamento. Tenho pouca experiência com eles, por isso não posso falar com as melhores práticas lá. Em C ++ / Java / C # embora ... não é bom.

Telastyn
fonte
Estou confuso com seus dois parágrafos do meio. O que você quer dizer com 'subtipo'? Estou usando interfaces, que são contratos, não implementações, e não tenho certeza de como seus comentários se aplicam. O que você quer dizer com 'sem digitação'? O que você quer dizer com "implícito"? Um IDog pode fazer tudo o que um IAnimal pode, mas não é garantido que você poderá fazer mais.
Kendall Frey
Obrigado pelo esclarecimento. No meu caso específico, não estou usando exatamente as interfaces de maneira diferente. Provavelmente, pode ser melhor descrito dizendo que tenho uma Kennelclasse que armazena uma lista de IDogs.
Kendall Frey
@KendallFrey: Ou você está descartando a interface (ruim) ou está criando uma distinção artificial que cheira a uma abstração incorreta.
Telastyn
2
@Telastyn - Finalidade de como interface é que o fornecimento de um metadados
Freshblood
1
@Telastyn: Então, como os atributos se aplicam à verificação em tempo de compilação? AFAIK, em C #, atributos só podem ser usados ​​em tempo de execução.
Kendall Frey
2

Eu só conheço bem C #, então não posso dizer isso para programação OO em geral, mas como um exemplo C #, se você precisar categorizar classes, as opções óbvias são interfaces, propriedade enum ou atributos personalizados. Normalmente eu escolheria a interface, não importa o que os outros pensem.

if(animal is IDog)
{
}
if(animal.Type == AnimalType.Dog)
{
}
if(animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
{
}


var dogs1 = animals.OfType<IDog>();
var dogs2 = animals.Where(a => a.Type == AnimalType.Dog);
var dogs3 = animals.Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any());


var dogsFromDb1 = session.Query<IDog>();
var dogsFromDb2 = session.Query<Animal>().Where(a => a.Type == AnimalType.Dog);
var dogsFromDb3 = session.Query<Animal>().Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute),false).Any());


public void DoSomething(IDog dog)
{
}
public void DoSomething(Animal animal)
{
    if(animal.Type != AnimalType.Dog)
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}
public void DoSomething(Animal animal)
{
    if(!animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}
Mike Koder
fonte
A última seção do código é principalmente para o que eu uso isso. Percebo que a versão da interface é obviamente mais curta, além de ser verificada em tempo de compilação.
Kendall Frey
Eu tenho um caso de uso simillar mas a maioria dos desenvolvedores .net recomendo fortemente contra isso, mas eu não vou usar atributos
GorillaApe
-1

Não há nada de errado em ter uma interface que herda outra interface sem adicionar novos membros, se for esperado que todas as implementações dessa última interface possam fazer coisas úteis que algumas implementações da anterior podem não ter. De fato, é um bom padrão que o IMHO deveria ter sido usado mais em coisas como o .net Framework, especialmente porque um implementador cuja classe naturalmente satisfaria as restrições implícitas na interface mais derivada pode indicar que simplesmente implementando a interface mais derivada , sem precisar alterar nenhum código ou declaração de implementação de membro.

Por exemplo, suponha que eu tenha as interfaces:

interface IMaybeMutableFoo {
  Bar Thing {get; set;} // E outras propriedades também
  bool IsMutable;
  IImmutableFoo AsImmutable ();
}
interface IImmutableFoo: IMaybeMutableFoo {};

IImmutableFoo.AsImmutable()Espera-se que uma implementação de retorne a si mesma; IMaybeMutableFoo.AsImmutable()seria esperado que uma implementação de classe mutável retornasse um novo objeto profundamente imutável contendo um instantâneo dos dados. Uma rotina que deseja armazenar uma captura instantânea dos dados mantidos em um IMaybeMutableFoopode oferecer sobrecargas:

public void StoreData (IImmutableFoo ThingToStore)
{
  // Armazenar a referência será suficiente, pois ThingToStore é conhecido por ser imutável
}
publicDados públicos StoreData (IMaybeMutableFoo ThingToStore)
{
  StoreData (ThingToStore.AsImmutable ()); // Chame a sobrecarga mais específica
}

Nos casos em que o compilador não sabe que o objeto a ser armazenado é um IImmutableFoo, ele chamará AsImmutable(). A função deve retornar rapidamente se o item já estiver imutável, mas uma chamada de função através de uma interface ainda leva tempo. Se o compilador souber que o item já é um IImmutableFoo, poderá ignorar a chamada de função desnecessária.

supercat
fonte
Downvoter: Gostaria de comentar? Há momentos em que herdar uma interface sem adicionar nada de novo é bobagem, mas isso não significa que não há momentos em que é uma boa técnica (e IMHO subutilizada).
30816