web-dev-qa-db-fra.com

Quel est le but d'une interface de marqueur?

Quel est le but d'une interface de marqueur?

79
coder

Ceci est un peu une tangente basée sur la réponse de "Mitch Wheat". 

En général, chaque fois que je vois des gens citer les directives de conception de cadre, j'aime toujours mentionner que:

Vous devez généralement ignorer les directives de conception du cadre la plupart du temps.  

Ce n'est pas à cause d'un problème avec les directives de conception du cadre. Je pense que le framework .NET est une bibliothèque de classes fantastique. Une grande partie de cette fantaisie découle des directives de conception de cadre. 

Cependant, les directives de conception ne s'appliquent pas à la plupart des codes écrits par la plupart des programmeurs. Leur objectif est de permettre la création d'un grand framework utilisé par des millions de développeurs, et non de rendre plus efficace l'écriture de bibliothèque.

De nombreuses suggestions peuvent vous guider dans des tâches qui:

  1. Peut-être pas le moyen le plus simple de mettre en œuvre quelque chose
  2. Peut entraîner une duplication supplémentaire du code
  3. Peut avoir des frais généraux d'exécution supplémentaires

Le framework .net est grand, vraiment gros. C'est tellement énorme qu'il serait absolument déraisonnable de supposer que quiconque a une connaissance détaillée de chaque aspect de la question. En fait, il est beaucoup plus prudent de supposer que la plupart des programmeurs rencontrent souvent des parties du cadre qu’ils n’ont jamais utilisées auparavant.

Dans ce cas, les principaux objectifs d’un concepteur d’API sont les suivants:

  1. Gardez les choses cohérentes avec le reste du cadre
  2. Éliminer la complexité inutile dans la surface de l'API

Les instructions de conception du cadre Incitez les développeurs à créer un code permettant d'atteindre ces objectifs.

Cela signifie éviter des couches d'héritage, même si cela implique de dupliquer du code, ou de pousser toutes les exceptions renvoyées du code vers des "points d'entrée" plutôt que d'utiliser des aides partagées (pour que les traces de pile aient plus de sens dans le débogueur), et beaucoup d'autres choses similaires.

La principale raison pour laquelle ces instructions suggèrent d'utiliser des attributs plutôt que des interfaces de marqueur est que la suppression de ces interfaces rend la structure d'héritage de la bibliothèque de classes beaucoup plus accessible. Un diagramme de classes avec 30 types et 6 couches de hiérarchie d'héritage est très décourageant par rapport à un diagramme de 15 types et 2 couches de hiérarchie. 

S'il y a vraiment des millions de développeurs utilisant vos API, ou si votre base de code est vraiment grosse (disons plus de 100K LOC), alors suivre ces instructions peut aider beaucoup.

Si 5 millions de développeurs passent 15 minutes à apprendre une API plutôt qu'à 60 minutes, il en résulte une économie nette de 428 années-homme. Ça fait beaucoup de temps.

Cependant, la plupart des projets n'impliquent pas des millions de développeurs, ni 100K + LOC. Dans un projet typique, avec 4 développeurs et environ 50 000 loc., Les hypothèses sont très différentes. Les développeurs de l'équipe comprendront beaucoup mieux le fonctionnement du code. Cela signifie qu’il est beaucoup plus logique d’optimiser pour produire rapidement un code de haute qualité et pour réduire le nombre de bogues et l’effort nécessaire pour apporter des modifications. 

Passer 1 semaine à développer du code compatible avec le framework .net, par opposition à 8 heures pour écrire du code facile à modifier et comportant moins de bugs peut entraîner:

  1. Projets en retard
  2. Bons bonus
  3. Augmentation du nombre de bogues
  4. Plus de temps passé au bureau et moins de temps sur la plage à boire des margaritas.

Sans 4,999,999 autres développeurs à absorber les coûts, il n'en vaut généralement pas la peine.

Par exemple, le test des interfaces de marqueur se résume à une seule expression "est" et entraîne moins de code que la recherche d'attributs. 

Donc, mon conseil est:

  1. Suivez les directives du cadre de manière religieuse si vous développez des bibliothèques de classes (ou des widgets d'interface utilisateur) destinés à une consommation étendue.
  2. Pensez à en adopter si vous avez plus de 100 000 lettres de crédit dans votre projet.
  3. Sinon, ignorez-les complètement.
71
Scott Wisniewski

Les interfaces de marqueur sont utilisées pour marquer la capacité d'une classe d'implémenter une interface spécifique au moment de l'exécution. 

Les directives Interface Design et .NET concernant la conception de types - Interface Design découragent l'utilisation d'interfaces de marqueur en faveur de l'utilisation d'attributs en C #, mais comme @Jay Bazuzi l'a souligné, il est plus facile de vérifier les interfaces de marqueur que pour les attributs: o is I 

Donc au lieu de cela:

public interface IFooAssignable {} 

public class FooAssignableAttribute : IFooAssignable 
{
    ...
}

Les directives .NET vous recommandaient de procéder comme suit:

public class FooAssignableAttribute : Attribute 
{
    ...
}

[FooAssignable]
public class Foo 
{    
   ...
} 
41
Mitch Wheat

Comme toutes les autres réponses ont indiqué "elles devraient être évitées", il serait utile de savoir pourquoi.

Premièrement, pourquoi les interfaces de marqueur sont utilisées: elles existent pour permettre au code qui utilise l’objet qui l’implémente de vérifier s’ils implémentent ladite interface et traitent l’objet différemment s’il le fait.

Le problème avec cette approche est qu’elle casse l’encapsulation. L'objet lui-même a maintenant un contrôle indirect sur la manière dont il sera utilisé à l'extérieur. De plus, il a connaissance du système dans lequel il va être utilisé. En appliquant l'interface du marqueur, la définition de la classe suggère qu'elle s'attend à être utilisée à un endroit qui vérifie l'existence du marqueur. Il possède une connaissance implicite de l'environnement dans lequel il est utilisé et tente de définir son utilisation. Cela va à l’encontre de l’idée d’encapsulation car il a une connaissance de la mise en œuvre d’une partie du système qui n’existe pas du tout.

Sur le plan pratique, cela réduit la portabilité et la réutilisabilité. Si la classe est réutilisée dans une application différente, l'interface doit également être copiée et elle risque de ne pas avoir de signification dans le nouvel environnement, ce qui la rend entièrement redondante.

En tant que tel, le "marqueur" est une métadonnée sur la classe. Ces métadonnées ne sont pas utilisées par la classe elle-même et ne sont significatives que pour le code client externe (certains!), De sorte qu'elle puisse traiter l'objet d'une certaine manière. Parce que cela n'a de sens que pour le code client, les métadonnées doivent être dans le code client, pas dans l'API de classe.

La différence entre une "interface marqueur" et une interface normale est qu’une interface avec des méthodes indique au monde extérieur comment elle peut être utilisée peut alors qu’une interface vide implique qu’elle raconte au monde extérieur comment elle peut devrait être utilisé.

22
Tom B

Les interfaces de marqueur peuvent parfois être un mal nécessaire lorsqu'un langage ne supporte pas les types union discriminée

Supposons que vous vouliez définir une méthode qui attend un argument dont le type doit correspondre exactement à A, B ou C. Dans de nombreux langages fonctionnels (comme F # ), un tel type peut être défini de la manière suivante:

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

Cependant, dans les premières langues OO telles que C #, cela n'est pas possible. La seule façon de réaliser quelque chose de similaire ici est de définir l'interface IArg et de "marquer" A, B et C avec elle. 

Bien sûr, vous pouvez éviter d'utiliser l'interface marqueur en acceptant simplement le type "objet" en tant qu'argument, mais vous perdriez alors l'expressivité et un certain degré de sécurité du type. 

Les types d'union discriminés sont extrêmement utiles et existent dans les langages fonctionnels depuis au moins 30 ans. Étrangement, à ce jour, tous les langages OO classiques ont ignoré cette fonctionnalité - bien qu’elle n’ait en fait rien à voir avec la programmation fonctionnelle en soi, mais appartient au système de types. 

7
Marc Sigrist

Une interface de marqueur est juste une interface qui est vide. Une classe implémenterait cette interface sous forme de métadonnées à utiliser pour une raison quelconque. En C #, vous utiliseriez plus couramment des attributs pour baliser une classe pour les mêmes raisons que vous utiliseriez une interface marqueur dans d'autres langues.

6
Richard Hein

Ces deux méthodes d'extension résoudront la plupart des problèmes que Scott affirme privilégier les interfaces de marqueur par rapport aux attributs:

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

Maintenant vous avez:

if (o.HasAttribute<FooAssignableAttribute>())
{
    //...
}

contre:

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

Je ne vois pas comment la création d'une API prendra 5 fois plus de temps avec le premier modèle que pour le second, comme le prétend Scott.

4
Mr Anderson

Une interface de marqueur permet à une classe d'être étiquetée d'une manière qui sera appliquée à toutes les classes descendantes. Une interface de marqueur "pure" ne définirait ni n'hériterait de rien; Un type d'interface marqueur plus utile peut être un type qui "hérite" d'une autre interface mais ne définit aucun nouveau membre. Par exemple, s'il existe une interface "IReadableFoo", vous pouvez également définir une interface "IImmutableFoo", qui se comporterait comme un "Foo" mais promettrait à quiconque l'utilisant que rien ne changerait sa valeur. Une routine qui accepte un IImmutableFoo pourrait l'utiliser comme un IReadableFoo, mais n'accepterait que les classes déclarées comme implémentant IImmutableFoo.

Je ne peux pas penser à beaucoup d'utilisations pour des interfaces de marqueur "pur". Le seul auquel je puisse penser serait si EqualityComparer (de T) .Default retournait Object.Equals pour tout type ayant implémenté IDoNotUseEqualityComparer, même si le type implémentait également IEqualityComparer. Cela permettrait d'avoir un type immuable non scellé sans enfreindre le principe de substitution de Liskov: si le type scelle toutes les méthodes liées au test d'égalité, un type dérivé pourrait ajouter des champs supplémentaires et les rendre mutables, mais la mutation de ces champs ne serait pas. t être visible en utilisant des méthodes de type base. Il ne serait peut-être pas horrible d'avoir une classe immuable non scellée et d'éviter toute utilisation de EqualityComparer.Default ou des classes dérivées trust de ne pas implémenter IEqualityComparer, mais une classe dérivée qui implémenterait IEqualityComparer pourrait apparaître comme une classe mutable même lorsqu'elle était vue objet de classe.

4
supercat

L’interface marqueur est en réalité juste une programmation procédurale dans un langage OO . Une interface définit un contrat entre les développeurs et les consommateurs, à l’exception d’une interface marqueur, car une interface marqueur ne définit rien d’autre. Donc, dès la sortie de la porte, l’interface marqueur échoue à son objectif fondamental d’être une interface.

1
Ali Bayat

L'interface de marqueur est une interface totalement vierge qui n'a pas de corps/de membres de données/d'implémentation.
Une interface de classe implémente lorsque cela est nécessaire, il suffit simplement de " marquer "; Cela signifie qu'il indique à la machine virtuelle Java que la classe en question est utilisée à des fins de clonage, alors laissez-la cloner. Cette classe particulière doit sérialiser ses objets, veuillez donc permettre à ses objets d'être sérialisés.

0
Arun Raaj

Les marqueurs sont des interfaces vides. Un marqueur est soit là ou il ne l'est pas. 

classe Foo: IConfidentiel

Ici, nous signalons que Foo est confidentiel. Aucune propriété ou attribut supplémentaire réel requis. 

0
Rick O'Shea