Qual é a finalidade de uma interface de marcador?
Qual é a finalidade de uma interface de marcador?
Respostas:
Isso é um pouco tangente com base na resposta de "Mitch Wheat".
Geralmente, sempre que vejo pessoas citarem as diretrizes de design da estrutura, sempre gosto de mencionar que:
Geralmente, você deve ignorar as diretrizes de design da estrutura na maioria das vezes.
Isso não é devido a qualquer problema com as diretrizes de design da estrutura. Acho que o .NET framework é uma biblioteca de classes fantástica. Muito desse fantástico flui das diretrizes de design da estrutura.
No entanto, as diretrizes de design não se aplicam à maioria dos códigos escritos pela maioria dos programadores. Seu objetivo é permitir a criação de uma grande estrutura usada por milhões de desenvolvedores, não para tornar a escrita da biblioteca mais eficiente.
Muitas das sugestões nele podem orientá-lo a fazer coisas que:
A estrutura .net é grande, muito grande. É tão grande que seria absolutamente irracional supor que alguém tenha conhecimento detalhado sobre cada aspecto dele. Na verdade, é muito mais seguro presumir que a maioria dos programadores frequentemente encontra partes da estrutura que nunca usaram antes.
Nesse caso, os objetivos principais de um designer de API são:
As diretrizes de design da estrutura levam os desenvolvedores a criar um código que atenda a esses objetivos.
Isso significa fazer coisas como evitar camadas de herança, mesmo que signifique duplicar o código, ou enviar todas as exceções lançando o código para "pontos de entrada" em vez de usar ajudantes compartilhados (para que os rastreamentos de pilha façam mais sentido no depurador), e muito de outras coisas semelhantes.
O principal motivo pelo qual essas diretrizes sugerem o uso de atributos em vez de interfaces de marcador é porque a remoção das interfaces de marcador torna a estrutura de herança da biblioteca de classes muito mais acessível. Um diagrama de classes com 30 tipos e 6 camadas de hierarquia de herança é muito assustador em comparação com outro 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, mais de 100K LOC), seguir essas diretrizes pode ajudar muito.
Se 5 milhões de desenvolvedores gastam 15 minutos aprendendo uma API em vez de 60 minutos aprendendo, o resultado é uma economia líquida de 428 anos-homem. É muito tempo.
A maioria dos projetos, entretanto, não envolve milhões de desenvolvedores, ou 100K + LOC. Em um projeto típico, digamos com 4 desenvolvedores e cerca de 50K loc, o conjunto de premissas é 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 para produzir código de alta qualidade rapidamente 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, em comparação com 8 horas escrevendo código fácil de alterar e com menos bugs pode resultar em:
Sem 4.999.999 outros desenvolvedores para absorver os custos, geralmente não vale a pena.
Por exemplo, o teste de interfaces de marcador se resume a uma única expressão "é" e resulta em menos código do que a procura de atributos.
Portanto, meu conselho é:
virtual protected
método de modelo em DoSomethingCore
vez de DoSomething
não é muito trabalho adicional e você comunica claramente que é um método de modelo ... IMNSHO, as pessoas que escrevem aplicativos sem considerar a API ( But.. I'm not a framework developer, I don't care about my API!
) são exatamente aquelas pessoas que escrevem muitos duplicados ( e também código não documentado e geralmente ilegível, e não o contrário.
Marker Interfaces são usadas para marcar a capacidade de uma classe de implementação de uma interface específica em tempo de execução.
As Diretrizes de Design de Interface e de Design de Tipo .NET - Design de Interface desencorajam 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 marcador do que atributos:o is I
Então, em vez 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 afirmam "eles devem ser evitados", seria útil ter uma explicação do porquê.
Em primeiro lugar, por que as interfaces de marcador são usadas: elas existem para permitir que o código que está usando o objeto que o implementa verifique se eles implementam essa interface e tratem o objeto de forma diferente, se o fizer.
O problema com essa abordagem é que ela quebra o encapsulamento. O próprio objeto agora tem controle indireto sobre como 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 classe sugere que ela espera ser usada em algum lugar que verifique a existência do marcador. Ele tem conhecimento implícito do ambiente em que é usado e está tentando definir como deve ser usado. Isso vai contra a ideia de encapsulamento porque tem conhecimento da implementação de uma parte do sistema que existe inteiramente fora de seu próprio escopo.
Em um nível prático, isso reduz a portabilidade e a reutilização. Se a classe for reutilizada em um aplicativo diferente, a interface também precisa ser copiada e pode não ter nenhum significado no novo ambiente, tornando-a totalmente redundante.
Como tal, o "marcador" são metadados sobre a classe. Esses metadados não são usados pela própria classe e são significativos apenas para (alguns!) Códigos de cliente externos, de forma que possam tratar o objeto de uma determinada maneira. Por ter significado apenas para o código do cliente, os metadados devem estar no código do cliente, não na API da classe.
A diferença entre uma "interface de marcador" e uma interface normal é que uma interface com métodos informa ao mundo externo como ela pode ser usada, enquanto uma interface vazia indica que está dizendo ao mundo externo como deve ser usada.
IConstructableFromString<T>
especifica que uma classe T
só pode implementar IConstructableFromString<T>
se tiver um membro estático ...
public static T ProduceFromString(String params);
, uma classe complementar à interface pode oferecer um método public static T ProduceFromString<T>(String params) where T:IConstructableFromString<T>
; se o código do cliente tivesse um método como T[] MakeManyThings<T>() where T:IConstructableFromString<T>
, seria possível definir novos tipos que poderiam funcionar com o código do cliente sem ter que modificar o código do cliente para lidar com eles. Se os metadados estivessem no código do cliente, não seria possível criar novos tipos para uso pelo cliente existente.
T
e a classe que o usa é IConstructableFromString<T>
onde você tem um método na interface que descreve algum comportamento, então não é uma interface de marcador.
ProduceFromString
no exemplo acima não envolveria a interface de qualquer forma, exceto que a interface seria usada como um marcador para indicar quais classes deveriam implementar a função necessária.
As interfaces de marcador podem às vezes ser um mal necessário quando um idioma 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 primeiro (como F # ), tal tipo pode ser claramente definido como:
type Arg =
| AArg of A
| BArg of B
| CArg of C
No entanto, em linguagens OO-first, 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 o uso da interface do marcador simplesmente aceitando o tipo "objeto" como argumento, mas então você perderia expressividade e algum grau de segurança de tipo.
Os tipos de união discriminados são extremamente úteis e existem nas linguagens funcionais há pelo menos 30 anos. Estranhamente, até hoje, todas as principais linguagens OO ignoraram esse recurso - embora na verdade não tenha nada a ver com a programação funcional em si, mas pertença ao sistema de tipos.
Foo<T>
terá um conjunto separado de campos estáticos para cada tipo T
, não é difícil ter uma classe genérica contendo campos estáticos contendo delegados para processar um T
e pré-preencher esses campos com funções para lidar com cada tipo que a classe é suposto trabalhar. O uso de uma restrição de interface genérica sobre o tipo T
verificaria, no momento do compilador, se o tipo fornecido pelo menos afirmava ser válido, embora não fosse capaz de garantir que realmente fosse.
Uma interface de marcador é apenas uma interface vazia. Uma classe implementaria essa interface como metadados a serem usados por algum motivo. Em C #, você usaria mais comumente atributos para marcar uma classe pelos mesmos motivos que usaria uma interface de marcador em outras linguagens.
Uma interface de marcador permite que uma classe seja marcada de uma forma que será aplicada a todas as classes descendentes. Uma interface de marcador "pura" não definiria ou herdaria nada; um tipo mais útil de interfaces de marcador pode ser aquele que "herda" outra interface, mas não define novos membros. Por exemplo, se houver uma interface "IReadableFoo", também se pode definir uma interface "IImmutableFoo", que se comportaria como um "Foo", mas prometeria a quem a usasse que nada mudaria seu valor. Uma rotina que aceita um IImmutableFoo seria capaz de usá-lo como faria com um IReadableFoo, mas a rotina só aceitaria classes que foram declaradas como implementando IImmutableFoo.
Não consigo pensar em muitos usos para interfaces de marcador "puras". O único em que consigo pensar seria se EqualityComparer (de T) .Default retornaria Object.Equals para qualquer tipo que implementasse IDoNotUseEqualityComparer, mesmo se o tipo também implementasse IEqualityComparer. Isso permitiria ter um tipo imutável não lacrado sem violar o Princípio de Substituição de Liskov: se o tipo selar todos os métodos relacionados ao teste de igualdade, um tipo derivado poderia adicionar campos adicionais e torná-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 classe imutável sem lacre e evitar qualquer uso de EqualityComparer.Default ou classes derivadas de confiança para não implementar IEqualityComparer,
Esses dois métodos de extensão resolverão a maioria dos problemas que Scott afirma favorecer interfaces de marcador em vez de atributos:
public static bool HasAttribute<T>(this ICustomAttributeProvider self)
where T : Attribute
{
return self.GetCustomAttributes(true).Any(o => o is T);
}
public static bool HasAttribute<T>(this object self)
where T : Attribute
{
return self != null && self.GetType().HasAttribute<T>()
}
Agora você tem:
if (o.HasAttribute<FooAssignableAttribute>())
{
//...
}
versus:
if (o is IFooAssignable)
{
//...
}
Não consigo ver como construir uma API levará 5 vezes mais tempo com o primeiro padrão em comparação com o segundo, como afirma Scott.
Os marcadores são interfaces vazias. Um marcador está lá ou não está.
classe Foo: IConfidential
Aqui, marcamos Foo como confidencial. Não são necessárias propriedades ou atributos adicionais reais.
A interface do marcador é uma interface totalmente em branco que não possui corpo / membros de dados / implementação.
Uma classe implementa interface de marcador quando necessário, é apenas para " marcar "; significa que ele informa à JVM que a classe específica é para fins de clonagem, portanto, permita a sua clonagem. Esta classe específica serve para serializar seus objetos, portanto, permita que seus objetos sejam serializados.
A interface do marcador é realmente apenas uma programação procedural em uma linguagem OO. Uma interface define um contrato entre implementadores e consumidores, exceto uma interface de marcador, porque uma interface de marcador define nada além de si mesma. Assim, logo de cara, a interface do marcador falha no propósito básico de ser uma interface.