Qual é o propósito de uma interface de marcadores?

Qual é o propósito de uma interface de marcadores?

Isso é um pouco tangente com base na resposta de “Mitch Wheat”.

Geralmente, sempre que vejo pessoas citarem as diretrizes de design do framework, sempre gosto de mencionar que:

Você geralmente deve ignorar as diretrizes de design do framework na maioria das vezes.

Isso não é devido a qualquer problema com as diretrizes de design do framework. Eu acho que o .NET framework é uma fantástica biblioteca de classs. Muito desse fantástico flui das diretrizes de design do framework.

No entanto, as diretrizes de design não se aplicam à maioria dos códigos escritos pela maioria dos programadores. Sua finalidade é permitir a criação de uma estrutura grande que é usada por milhões de desenvolvedores, não para tornar a escrita na biblioteca mais eficiente.

Muitas das sugestões podem orientá-lo a fazer coisas que:

  1. Pode não ser a maneira mais direta de implementar algo
  2. Pode resultar em duplicação de código extra
  3. Pode ter sobrecarga de tempo de execução extra

O framework .net é grande, muito grande. É tão grande que seria absolutamente irracional assumir que alguém tenha conhecimento detalhado de cada aspecto. De fato, é muito mais seguro supor que a maioria dos programadores frequentemente encontra partes da estrutura que nunca usaram antes.

Nesse caso, os principais objectives de um designer de API são:

  1. Mantenha as coisas consistentes com o restante do framework
  2. Elimine a complexidade desnecessária na área de superfície da API

As diretrizes de design da estrutura incentivam os desenvolvedores a criar códigos que cumpram essas metas.

Isso significa fazer coisas como evitar camadas de inheritance, mesmo que isso signifique duplicar código ou empurrar todos os códigos de exceção para “pontos de input” em vez de usar ajudantes compartilhados (para que os rastreamentos de pilha façam mais sentido no depurador) e muito de outras coisas semelhantes.

A principal razão pela qual essas diretrizes sugerem o uso de atributos em vez de interfaces de marcadores é porque a remoção das interfaces do marcador torna a estrutura de inheritance da biblioteca de classs muito mais acessível. Um diagrama de classs com 30 tipos e 6 camadas de hierarquia de inheritance é muito assustador comparado a um com 15 tipos e 2 camadas de hierarquia.

Se realmente houver milhões de desenvolvedores usando suas APIs, ou se sua base de código for muito grande (digamos, acima de 100K LOC), seguir essas diretrizes pode ajudar bastante.

Se 5 milhões de desenvolvedores gastarem 15 minutos aprendendo uma API, em vez de gastar 60 minutos aprendendo, o resultado é uma economia líquida de 428 anos-homem. Isso é muito tempo.

A maioria dos projetos, no entanto, não envolve milhões de desenvolvedores ou 100K + LOC. Em um projeto típico, com 4 desenvolvedores e cerca de 50K loc, o conjunto de suposições é muito diferente. Os desenvolvedores da equipe terão uma compreensão muito melhor de como o código funciona. Isso significa que faz muito mais sentido otimizar a produção rápida de código de alta qualidade e reduzir a quantidade de bugs e o esforço necessário para fazer alterações.

Gastar 1 semana desenvolvendo código consistente com a estrutura .net, contra 8 horas de código de escrita que é fácil de alterar e tem menos bugs, pode resultar em:

  1. Projetos atrasados
  2. Bônus mais baixos
  3. Contagens de bugs aumentadas
  4. Mais tempo gasto no escritório, e menos tempo na praia bebendo margaritas.

Sem 4.999.999 outros desenvolvedores para absorver os custos, geralmente não vale a pena.

Por exemplo, o teste de interfaces de marcadores se resume a uma única expressão “é” e resulta em menos código que procura atributos.

Então meu conselho é:

  1. Siga as diretrizes da estrutura religiosamente se você estiver desenvolvendo bibliotecas de classs (ou widgets de interface do usuário) destinadas a um consumo amplo.
  2. Considere adotar alguns deles se você tiver mais de 100K LOC em seu projeto
  3. Caso contrário, ignore-os completamente.

Interfaces de Marcador são usadas para marcar a capacidade de uma class como implementar uma interface específica em tempo de execução.

O Design de Interface e as Diretrizes de Design de Tipo .NET – Design de Interface desencoraja o uso de interfaces de marcador em favor do uso de atributos em C #, mas como @Jay Bazuzi aponta, é mais fácil verificar interfaces de marcadores do que atributos: o is I

Então, ao invés disso:

 public interface IFooAssignable {} public class FooAssignableAttribute : IFooAssignable { ... } 

As diretrizes do .NET recomendam que você faça isso:

 public class FooAssignableAttribute : Attribute { ... } [FooAssignable] public class Foo { ... } 

Uma vez que todas as outras respostas declararam “devem ser evitadas”, seria útil ter uma explicação do motivo.

Em primeiro lugar, por que as interfaces de marcador são usadas: elas existem para permitir que o código que está usando o object que o implementa verifique se implementa a referida interface e trata o object de maneira diferente se isso ocorrer.

O problema com essa abordagem é que ela quebra o encapsulamento. O object em si agora tem controle indireto sobre como ele será usado externamente. Além disso, ele tem conhecimento do sistema em que será usado. Ao aplicar a interface do marcador, a definição da class sugere que ele deve ser usado em algum lugar que verifique a existência do marcador. Ele tem conhecimento implícito do ambiente em que está sendo usado e está tentando definir como ele deve ser usado. Isso vai contra a idéia de encapsulamento porque tem conhecimento da implementação de uma parte do sistema que existe inteiramente fora de seu escopo.

Em um nível prático, isso reduz a portabilidade e a reutilização. Se a class for reutilizada em um aplicativo diferente, a interface também precisará ser copiada e pode não ter nenhum significado no novo ambiente, tornando-a totalmente redundante.

Como tal, o “marcador” é um metadado sobre a class. Esses metadados não são usados ​​pela própria class e são significativos apenas para (alguns!) Códigos de clientes externos para que possam tratar o object de uma certa maneira. Como ele só tem significado para o código do cliente, os metadados devem estar no código do cliente, não na API da class.

A diferença entre uma “interface de marcador” e uma interface normal é que uma interface com methods informa ao mundo exterior como ela pode ser usada, enquanto uma interface vazia implica dizer ao mundo externo como ela deve ser usada.

Uma interface de marcador é apenas uma interface vazia. Uma class implementaria essa interface como metadados a serem usados ​​por algum motivo. Em C # você usaria mais comumente atributos para marcar uma class pelas mesmas razões que você usaria uma interface de marcador em outros idiomas.

Interfaces de marcadores às vezes podem ser um mal necessário quando uma linguagem não suporta tipos de união discriminados .

Suponha que você queira definir um método que espera um argumento cujo tipo deve ser exatamente um de A, B ou C. Em muitas linguagens funcionais iniciais (como F # ), esse tipo pode ser definido como:

 type Arg = | AArg of A | BArg of B | CArg of C 

No entanto, em primeiras linguagens OO, como C #, isso não é possível. A única maneira de conseguir algo semelhante aqui é definir a interface IArg e “marcar” A, B e C com ela.

Claro, você poderia evitar usar a interface do marcador simplesmente aceitando o tipo “object” como argumento, mas então você perderia a expressividade e algum grau de segurança do tipo.

Os tipos de união discriminados são extremamente úteis e existem em linguagens funcionais há pelo menos 30 anos. Estranhamente, até hoje, todas as principais linguagens OO ignoraram esse recurso – embora ele não tenha nada a ver com functional programming em si, mas pertence ao sistema de tipos.

Uma interface de marcador permite que uma class seja marcada de maneira a ser aplicada a todas as classs descendentes. Uma interface de marcador “pura” não definiria nem herdaria nada; um tipo mais útil de interfaces de marcadores pode ser aquele que “herda” outra interface mas não define novos membros. Por exemplo, se houver uma interface “IReadableFoo”, também é possível definir uma interface “IImmutableFoo”, que se comportaria como um “Foo”, mas prometeria a qualquer um que a utilize que nada mudaria seu valor. Uma rotina que aceita um IImmutableFoo seria capaz de usá-lo como se fosse um IReadableFoo, mas a rotina só aceitaria classs que foram declaradas como implementando IImmutableFoo.

Não consigo pensar em muitos usos para interfaces de marcadores “puros”. O único que posso pensar seria se EqualityComparer (de T) .Default retornaria Object.Equals para qualquer tipo que implementou IDoNotUseEqualityComparer, mesmo se o tipo também implementou IEqualityComparer. Isto permitiria ter um tipo imutável não selado sem violar o Princípio de Substituição de Liskov: se o tipo sela todos os methods relacionados ao teste de igualdade, um tipo derivado poderia adicionar campos adicionais e tê-los mutáveis, mas a mutação de tais campos não seria t ser visível usando qualquer método do tipo base. Pode não ser horrível ter uma class imutável não selada e evitar o uso de EqualityComparer.Default ou confiar em classs derivadas para não implementar IEqualityComparer, mas uma class derivada que implementasse IEqualityComparer poderia aparecer como uma class mutável mesmo quando vista como uma base. object de class.

Esses dois methods de extensão resolverão a maioria dos problemas que Scott afirma favorecerem as interfaces dos marcadores sobre os atributos:

 public static bool HasAttribute(this ICustomAttributeProvider self) where T : Attribute { return self.GetCustomAttributes(true).Any(o => o is T); } public static bool HasAttribute(this object self) where T : Attribute { return self != null && self.GetType().HasAttribute() } 

Agora você tem:

 if (o.HasAttribute()) { //... } 

versus:

 if (o is IFooAssignable) { //... } 

Não consigo ver como a criação de uma API levará 5 vezes mais tempo com o primeiro padrão em comparação com o segundo, como afirma Scott.

A interface do marcador é realmente apenas uma programação processual em uma linguagem OO. Uma interface define um contrato entre implementadores e consumidores, exceto uma interface de marcador, porque uma interface de marcador não define nada além de si mesmo. Então, logo no início, a interface do marcador falha no propósito básico de ser uma interface.

Marcadores são interfaces vazias. Um marcador está lá ou não está.

class Foo: IConfidential

Aqui nós marcamos Foo como sendo confidencial. Nenhuma propriedade ou atributo adicional real é necessário.

A interface do marcador é uma interface em branco total que não possui body / data-members / implementation.
Uma class implementa a interface do marcador quando necessário, é apenas para ” marcar “; significa que ele informa à JVM que a class específica tem o propósito de clonar, portanto, permita que ela seja clonada. Essa class em particular é para serializar seus objects, portanto, permita que seus objects sejam serializados.