Comment ne pas utiliser les interfaces en C #

Lors de la conception d'une application, vous devrez souvent utiliser des interfaces et des classes abstraites. Cet article présente quelques exemples courants «d'abus d'interface» et les stratégies que nous pouvons utiliser pour les éviter. Il traite également de ce que signifie le principe, «programme vers une interface et non vers une implémentation».

Que sont les interfaces?

Tout d'abord, comprenons les interfaces et pourquoi elles sont nécessaires dans la programmation. Une interface est strictement un contrat; il n'a aucune implémentation. Une interface contient uniquement des déclarations de membres. Vous pouvez avoir des déclarations de méthode mais pas des définitions. Les membres déclarés dans une interface doivent être implémentés dans les types (classes et structures) qui étendent ou implémentent l'interface. Une interface ne peut pas contenir de champs. Une interface ne peut pas être sérialisée car elle ne peut pas avoir de membres de données. Comme je l'ai dit, une interface ne peut avoir que des déclarations et non des définitions.  

Évitez de modifier les interfaces 

Une classe ou une structure qui étend une interface doit implémenter tous ses membres. Si l'implémentation change, votre code fonctionnera toujours. Cependant, si le contrat, c'est-à-dire l'interface, change, vous devrez alors changer les implémentations de tous les types qui étendent l'interface. En d'autres termes, toute modification de l'interface aura un impact sur tous les types qui étendent l'interface. Les types étendant l'interface doivent adhérer au contrat. Par conséquent, n'utilisez les interfaces que lorsque vous avez rarement besoin de les modifier. En outre, il est généralement préférable de créer une nouvelle interface que de modifier une interface existante. 

Programmez vers une interface, pas vers une implémentation

Vous avez peut-être entendu les mots «programme vers une interface et non vers une implémentation» de temps en temps. Vous utilisiez peut-être des interfaces dans votre code mais vous programmiez toujours l'implémentation. Examinons maintenant la différence entre les deux approches.

Lorsque vous programmez sur une interface, vous utilisez l'abstraction la plus générique (une interface ou une classe abstraite) au lieu d'une implémentation concrète. Étant donné que les interfaces garantissent l'uniformité, la programmation sur une interface implique que vous pouvez gérer des objets similaires de manière uniforme. Ce faisant, vous êtes découplé de l'implémentation - c'est-à-dire que vos implémentations peuvent varier. Cela ajoute également de la flexibilité à vos conceptions.

L'extrait de code suivant illustre la programmation sur une interface. Considérez une interface nommée IRepository qui contient la déclaration de quelques méthodes. Les classes ProductRepository et CustomerRepository étendent l'interface IRepository et implémentent les méthodes déclarées dans l'interface IRepository, comme indiqué ci-dessous.

interface publique IRepository

    {

        // Un peu de code

    }

    classe publique ProductRepository: IRepository

    {

        // Un peu de code

    }

    classe publique CustomerRepository: IRepository

    {

        // Un peu de code

    }

Le code suivant peut être utilisé pour créer une instance de ProductRepository.

IRepository repository = nouveau ProductRepository ();

L'idée est que vous pouvez utiliser ici n'importe quelle classe qui implémente l'interface IRepository. Ainsi, la déclaration suivante est également valide.

IRepository repository = nouveau CustomerRepository ();

Lorsque vous programmez une implémentation, cette uniformité est perdue. Au lieu de cela, vous aurez généralement des constructions, telles que les instructions «if..else» ou «switch..case», pour contrôler le comportement de votre code.

Évitez la surutilisation des interfaces

Associer chaque classe à une interface n'est pas une bonne pratique. La surutilisation des interfaces de cette manière crée une complexité inutile, introduit une redondance du code, viole YAGNI et réduit la lisibilité et la maintenabilité de la base de code. Les interfaces sont utilisées pour regrouper des objets ayant un comportement identique. Si les objets n'ont pas le même comportement, ce regroupement n'est pas nécessaire. L'utilisation d'interfaces lorsque vous n'allez pas en avoir plusieurs implémentations est un exemple de surutilisation d'interface.

La création d'une interface pour une classe qui correspond aux membres publics de la classe est assez courante. Ce faisant, vous n'ajoutez aucune valeur du tout - vous dupliquez simplement l'interface de la classe sans ajouter de véritable abstraction.

Regardons maintenant un exemple de la surutilisation des interfaces. Considérez l'interface suivante nommée IProduct.

interface publique IProduct

    {

        int Id {get; ensemble; }

        string ProductName {get; ensemble; }

        double Price {get; ensemble; }

        int Quantity {get; ensemble; }

    }

La classe Product étend l'interface IProduct comme indiqué ci-dessous.

Classe publique Produit: IProduct

    {

        public int Id {get; ensemble; }

        chaîne publique ProductName {get; ensemble; }

        Prix ​​double public {get; ensemble; }

        public int Quantity {get; ensemble; }

    }

De toute évidence, nous n'avons pas besoin de l'interface IProduct, car l'interface et son implémentation sont identiques. Le code redondant n'est pas nécessaire.

Regardons un autre exemple. L'extrait de code suivant montre une interface nommée IProductManager ayant la déclaration de deux méthodes, à savoir Save et Update.

 interface publique IProductManager

    {

        void Save (produit IProduct);

        void Update (produit IProduct);

    }

L'interface IProductManager contient les déclarations des méthodes publiques de la classe ProductManager. Voici à quoi ressemble la classe ProductManager.

 classe publique ProductManager: IProductManager

    {

        public void Save (produit IProduct)

        {

           // Écrivez votre implémentation ici

        }

        Mise à jour publique void (produit IProduct)

        {

            // Écrivez votre implémentation ici

        }

    }

Les interfaces IProduct et IProductManager sont des exemples de surutilisation d'interface. Ces deux interfaces ont une seule implémentation et elles n'ajoutent aucune valeur du tout.

En utilisant des interfaces, vous pouvez supprimer les couplages inutiles dans votre code et rendre votre code facilement testable. Cependant, la surutilisation des interfaces doit être évitée. N'utilisez les interfaces que lorsqu'il y en aura plus d'une implémentation. Vous pouvez également utiliser des interfaces lorsque vous avez une classe qui a de nombreux rôles à jouer ou qui a plusieurs responsabilités. Dans ce cas, votre classe peut implémenter plusieurs interfaces - une pour chaque rôle.