30 votes

Qu'y a-t-il de mal à tester un objet pour voir s'il implémente une interface ?

Dans les commentaires de cette réponse il est indiqué que " vérifier si l'objet a implémenté l'interface aussi répandue soit-elle, est une mauvaise chose. "

Vous trouverez ci-dessous ce que je crois être un exemple de cette pratique :

public interface IFoo
{
   void Bar();
}

public void DoSomething(IEnumerable<object> things)
{
   foreach(var o in things)
   {
       if(o is IFoo)
           ((IFoo)o).Bar();
   }
}

Ma curiosité étant piquée au vif car j'ai déjà utilisé des variantes de ce modèle, j'ai cherché un bon exemple ou une explication des raisons pour lesquelles c'est une mauvaise chose, mais je n'en ai pas trouvé.

Bien qu'il soit très possible que j'aie mal compris le commentaire, quelqu'un peut-il me fournir un exemple ou un lien pour mieux expliquer le commentaire ?

36voto

Jon Skeet Points 692016

Cela dépend de ce que vous essayez de faire. Parfois, cela peut être approprié - par exemple :

  • LINQ to Objects, où il est utilisé pour optimiser des opérations telles que Count qui peut être réalisée plus efficacement sur un IList<T> via les membres spécialisés.
  • LINQ to XML, où il est utilisé pour fournir une méthode d'analyse de la qualité de l'eau. vraiment une API conviviale qui accepte un large éventail de types, en itérant sur les valeurs lorsque cela est approprié.
  • Si vous vouliez trouver tous les contrôles d'un certain type sous un contrôle particulier dans Windows Forms, vous voudriez vérifier si chaque contrôle est un conteneur pour déterminer s'il faut ou non effectuer une récursion.

Dans d'autres cas, c'est moins approprié et vous devriez considérer si vous pouvez changer le type de paramètre à la place. C'est définitivement une "odeur" - normalement, vous ne devriez pas vous préoccuper de l'utilisation de l'attribut mise en œuvre Vous devez simplement utiliser l'API fournie par le type de paramètre déclaré. Ceci est également connu comme une violation de la Principe de substitution de Liskov .

Quoi qu'en disent les développeurs dogmatiques qui nous entourent, il y a des moments où il faut tout simplement faire Je veux vérifier le type de temps d'exécution d'un objet. Il est difficile de remplacer object.Equals(object) correctement sans utiliser is / as / GetType par exemple :) Ce n'est pas toujours une mauvaise chose, mais cela devrait toujours vous faire envisager s'il y a une meilleure approche. Utilisez-la avec parcimonie, uniquement lorsqu'il s'agit véritablement de la conception la plus appropriée.


Personnellement, je préférerais écrire le code que vous avez montré comme ceci, remarquez :

public void DoSomething(IEnumerable<object> things)
{
    foreach(var foo in things.OfType<IFoo>())
    {
        foo.Bar();
    }
}

Il accomplit la même chose, mais d'une manière plus soignée :)

18voto

Erno de Weerd Points 30391

Je m'attendrais à ce que la méthode ressemble à ceci, cela semble beaucoup plus sûr :

public void DoSomething(IEnumerable<IFoo> things)
{
   foreach(var o in things)
   {
           o.Bar();
   }
}

Pour en savoir plus sur la violation du principe de Liskov : Qu'est-ce que le principe de substitution de Liskov ?

10voto

Eric Lippert Points 300275

Si vous voulez savoir pourquoi le commentateur a fait ce commentaire, le mieux est probablement de lui demander de s'expliquer.

Je ne considérerais pas le code que vous avez posté comme étant "mauvais". Une pratique plus "réellement" mauvaise est d'utiliser des interfaces en tant que marqueurs . C'est à dire que tu ne prévois pas d'aller en fait en utilisant une méthode de l'interface ; vous avez plutôt déclaré l'interface sur une classe comme un moyen de la décrire d'une certaine manière. Utilisez des attributs, et non des interfaces, comme marqueurs sur les classes.

Les interfaces de marqueurs sont dangereuses à plusieurs égards. Voici une situation réelle que j'ai rencontrée une fois, où un produit important a pris une mauvaise décision sur la base d'une interface de marqueur : http://blogs.msdn.com/b/ericlippert/archive/2004/04/05/108086.aspx

Cela dit, le compilateur C# lui-même utilise une "interface de marquage" dans une situation donnée. Mads raconte l'histoire ici : http://blogs.msdn.com/b/madst/archive/2006/10/10/what-is-a-collection_3f00_.aspx

6voto

koen Points 4770

L'une des raisons est qu'il y aura une dépendance à cette interface qui n'est pas immédiatement visible sans creuser dans le code.

4voto

Groky Points 6866

La déclaration

vérifier si l'objet a implémenté l'interface, aussi rampante soit-elle aussi répandue soit-elle, est une mauvaise chose

est trop dogmatique à mon avis. Comme d'autres personnes l'ont répondu, vous pouvez très bien passer une collection de IFoo à votre méthode et obtenir le même résultat.

Cependant, les interfaces peuvent être utiles pour ajouter des fonctionnalités optionnelles aux classes . Par exemple, le cadre .net fournit le IDataErrorInfo interface*. Lorsqu'elle est implémentée, elle indique à un consommateur que en plus de la fonctionnalité standard de la classe, elle peut également fournir des informations sur les erreurs .

Dans ce cas, les informations sur les erreurs sont facultatives. Un modèle de vue WPF peut ou non fournir des informations sur les erreurs. Sans l'interrogation des interfaces, cette fonctionnalité optionnelle ne serait pas possible sans des classes de base à la surface énorme.

* Ignorons pour l'instant la terrible conception de l'interface IDataErrorInfo.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X