Quel est le but d’une interface de marqueur?

Quel est le but d’une interface de marqueur?

C’est un peu une tangente basée sur la réponse de “Mitch Wheat”.

Généralement, chaque fois que je vois des personnes citer les directives de conception du cadre, j’aime toujours mentionner que:

Vous devez généralement ignorer les directives de conception du framework 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 du cadre.

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

Beaucoup de suggestions peuvent vous aider à faire des choses qui:

  1. Peut ne pas être le moyen le plus simple de mettre en œuvre quelque chose
  2. Peut entraîner une duplication de code supplémentaire
  3. Peut avoir un temps d’exécution supplémentaire

Le framework .net est grand, vraiment gros. C’est tellement grand qu’il serait absolument déraisonnable de supposer que n’importe qui a des connaissances détaillées sur chaque aspect de celui-ci. En fait, il est beaucoup plus sûr 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 objectives d’un concepteur d’API sont les suivants:

  1. Garder les choses compatibles avec le rest du cadre
  2. Élimine la complexité inutile dans la surface API

Les directives de conception de la structure poussent les développeurs à créer du code pour atteindre ces objectives.

Cela signifie, par exemple, éviter les couches d’inheritance, même si cela implique de dupliquer du code, ou pousser tous les codes générant les exceptions vers des «points d’entrée» plutôt que d’utiliser des helpers partagés d’autres choses similaires.

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

S’il y a vraiment des millions de développeurs qui utilisent vos API, ou si votre base de code est vraiment volumineuse (disons plus de 100 Ko de LOC), suivre ces directives peut être très utile.

Si 5 millions de développeurs consacrent 15 minutes à l’apprentissage d’une API au lieu de consacrer 60 minutes à l’apprendre, le résultat est une économie nette de 428 années-hommes. C’est beaucoup de temps.

Cependant, la plupart des projets n’impliquent pas des millions de développeurs, ni 100K + LOC. Dans un projet typique, avec par exemple 4 développeurs et environ 50K loc, l’ensemble des hypothèses est très différent. Les développeurs de l’équipe comprendront mieux le fonctionnement du code. Cela signifie qu’il est beaucoup plus judicieux d’optimiser la production de code de haute qualité rapidement et de réduire le nombre de bogues et les efforts nécessaires pour apporter des modifications.

Le fait de passer 1 semaine à développer un code compatible avec le framework .net, contre 8 heures d’écriture de code facile à modifier et comportant moins de bogues peut entraîner:

  1. Projets en retard
  2. Des bonus inférieurs
  3. Augmentation du nombre de bogues
  4. Plus de temps passé au bureau et moins de temps à la plage en buvant des margaritas.

Sans 4 999 999 autres développeurs pour absorber les coûts, cela ne vaut généralement pas la peine.

Par exemple, le test d’interfaces de marqueur se résume à une seule expression “is” et entraîne moins de code que la recherche d’atsortingbuts.

Donc, mon conseil est:

  1. Suivez scrupuleusement les directives du framework si vous développez des bibliothèques de classes (ou des widgets d’interface utilisateur) destinées à une consommation étendue.
  2. Pensez à en adopter certaines si vous avez plus de 100K LOC dans votre projet.
  3. Sinon, ignorez-les complètement.

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

Les directives de conception d’interface et de conception de type .NET – Interface Design découragent l’utilisation d’interfaces de marqueurs en faveur de l’utilisation d’atsortingbuts en C #, mais comme le souligne @Jay Bazuzi, il est plus facile de vérifier les interfaces

Donc au lieu de cela:

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

Les directives .NET recommandaient que vous fassiez ceci:

 public class FooAssignableAtsortingbute : Atsortingbute { ... } [FooAssignable] public class Foo { ... } 

Étant donné que toutes les autres réponses ont indiqué “elles devraient être évitées”, il serait utile d’expliquer pourquoi.

Tout d’abord, pourquoi les interfaces de marqueur sont utilisées: elles existent pour permettre au code qui utilise l’object qui l’implémente de vérifier si elles implémentent cette interface et traitent l’object différemment s’il le fait.

Le problème avec cette approche est qu’elle rompt l’encapsulation. L’object lui-même a maintenant un contrôle indirect sur la façon dont il sera utilisé en externe. De plus, il a connaissance du système dans lequel il va être utilisé. En appliquant l’interface de marqueur, la définition de classe suggère qu’elle s’attend à être utilisée quelque part pour vérifier l’existence du marqueur. Il a une connaissance implicite de l’environnement dans lequel il est utilisé et essaie de définir comment il devrait être utilisé. Cela va à l’encontre de l’idée d’encapsulation car elle a connaissance de la mise en œuvre d’une partie du système qui existe entièrement en dehors de sa propre scope.

Au niveau pratique, cela réduit la portabilité et la réutilisation. Si la classe est réutilisée dans une autre application, l’interface doit également être copiée, et il se peut qu’elle n’ait aucune signification dans le nouvel environnement, ce qui la rend totalement 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 certains (certains!) Clients externes, afin de pouvoir traiter l’object d’une certaine manière. Comme il n’a de sens que pour le code client, les métadonnées doivent se trouver dans le code client et non dans la classe API.

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, alors qu’une interface vide implique de dire au monde extérieur comment il doit être utilisé.

Une interface de marqueur est juste une interface vide. Une classe implémenterait cette interface en tant que métadonnées à utiliser pour une raison quelconque. En C #, vous utiliseriez plus souvent des atsortingbuts pour baliser une classe pour les mêmes raisons que vous utiliseriez une interface de marqueur dans d’autres langues.

Les interfaces de marqueur peuvent parfois être un mal nécessaire quand un langage ne supporte pas les types d’ union discriminés .

Supposons que vous vouliez définir une méthode qui attend un argument dont le type doit être exactement l’un de A, B ou C. Dans de nombreux langages fonctionnels (comme F # ), un tel type peut être défini proprement comme suit:

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

Cependant, dans les langages OO-first tels que C #, cela n’est pas possible. La seule façon d’obtenir quelque chose de similaire ici est de définir l’interface IArg et de “marquer” A, B et C avec.

Bien sûr, vous pourriez éviter d’utiliser l’interface de marqueur en acceptant simplement le type “object” comme argument, mais vous perdriez alors son expressivité et un certain degré de sécurité du type.

Les types d’union discriminés sont extrêmement utiles et existent dans les langues fonctionnelles depuis au moins 30 ans. Curieusement, à ce jour, tous les langages OO traditionnels ont ignoré cette fonctionnalité – bien qu’elle n’ait en réalité rien à voir avec la functional programming en soi, mais appartient au système de caractères.

Une interface de marqueur permet à une classe d’être balisée de manière à être appliquée à toutes les classes descendantes. Une interface de marqueur “pure” ne définirait ou n’hériterait de rien; Un type d’interface de marqueur plus utile peut être celui qui “hérite” d’une autre interface mais ne définit aucun nouveau membre. Par exemple, s’il existe une interface “IReadableFoo”, on pourrait aussi définir une interface “IImmutableFoo”, qui se comporterait comme un “Foo” mais promettrait à quiconque l’utilise que rien ne changerait sa valeur. Une routine qui accepte un IImmutableFoo pourrait l’utiliser comme un IReadableFoo, mais la routine n’accepterait que les classes déclarées comme implémentant IImmutableFoo.

Je ne peux pas penser à beaucoup d’utilisations pour les interfaces de marqueurs “pures”. Le seul que je puisse penser serait si EqualityComparer (of T) .Default renverrait Object.Equals pour tout type implémentant IDoNotUseEqualityComparer, même si le type implémentait aussi IEqualityComparer. Cela permettrait d’avoir un type immuable non scellé sans violer 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 append 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 classes dérivées EqualityComparer.Default ou trust pour ne pas implémenter IEqualityComparer, mais une classe dérivée qui implémentait IEqualityComparer pourrait apparaître comme une classe mutable object de classe.

Ces deux méthodes d’extension résoudront la plupart des problèmes que Scott affirme favoriser les interfaces de marqueur sur les atsortingbuts:

 public static bool HasAtsortingbute(this ICustomAtsortingbuteProvider self) where T : Atsortingbute { return self.GetCustomAtsortingbutes(true).Any(o => o is T); } public static bool HasAtsortingbute(this object self) where T : Atsortingbute { return self != null && self.GetType().HasAtsortingbute() } 

Maintenant vous avez:

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

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 le second, comme le prétend Scott.

L’interface marqueur est en réalité juste une programmation procédurale dans un langage OO. Une interface définit un contrat entre les exécutants et les consommateurs, à l’exception d’une interface de marqueur, car une interface de marqueur ne définit rien d’autre que lui-même. Donc, dès le départ, l’interface du marqueur échoue au but fondamental d’être une interface.

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

classe Foo: IConfidential

Ici, nous marquons Foo comme étant confidentiel. Aucune propriété ou atsortingbut supplémentaire réel requirejs.

L’interface de marqueur est une interface vierge totale sans corps / données / implémentation.
Une classe implémente l’ interface de marqueur lorsque cela est nécessaire, c’est juste pour ” marquer “; signifie qu’il dit à la JVM que la classe particulière est dans le but de cloner afin de permettre le clonage. Cette classe particulière consiste à sérialiser ses objects. Permettez donc à ses objects d’être sérialisés.