web-dev-qa-db-fra.com

Utiliser la classe abstraite en C # comme définition

En tant que développeur C++, je suis assez habitué aux fichiers d'en-tête C++ et je trouve avantageux d'avoir une sorte de "documentation" forcée à l'intérieur du code. J'ai généralement du mal à lire du code C # à cause de cela: je n'ai pas ce genre de carte mentale de la classe avec laquelle je travaille.

Supposons qu'en tant qu'ingénieur logiciel, je conçois le cadre d'un programme. Serait-il trop fou de définir chaque classe comme une classe abstraite non implémentée, de manière similaire à ce que nous ferions avec les en-têtes C++, et de laisser les développeurs l'implémenter?

Je suppose qu'il peut y avoir des raisons pour lesquelles quelqu'un pourrait trouver cela comme une terrible solution, mais je ne sais pas pourquoi. Que faudrait-il considérer pour une solution comme celle-ci?

21
Dani Barca Casafont

La raison pour laquelle cela a été fait en C++ était de rendre les compilateurs plus rapides et plus faciles à implémenter. Ce n'était pas une conception pour rendre la programmation plus facile.

Le but des fichiers d'en-tête était de permettre au compilateur d'effectuer une première passe super rapide pour connaître tous les noms de fonction attendus et leur allouer des emplacements de mémoire afin qu'ils puissent être référencés lorsqu'ils sont appelés dans des fichiers cp, même si la classe qui les définissait avait pas encore analysé.

Il n'est pas recommandé de répliquer une conséquence des anciennes limitations matérielles dans un environnement de développement moderne!

La définition d'une interface ou d'une classe abstraite pour chaque classe réduira votre productivité; qu'auriez-vous pu faire d'autre avec ce temps ? De plus, les autres développeurs ne suivront pas cette convention.

En fait, d'autres développeurs peuvent supprimer vos classes abstraites. Si je trouve une interface dans le code qui répond à ces deux critères, je la supprime et la refactorise en code: 1. Does not conform to the interface segregation principle2. Only has one class that inherits from it

L'autre chose est, il existe des outils inclus avec Visual Studio qui font ce que vous souhaitez accomplir automatiquement:

  1. Le Class View
  2. Le Object Browser
  3. Dans Solution Explorer vous pouvez cliquer sur les triangles pour agrandir les classes afin de voir leurs fonctions, paramètres et types de retour.
  4. Il y a trois petits menus déroulants sous les onglets de fichiers, le plus à droite répertorie tous les membres de la classe actuelle.

Faites un essai ci-dessus avant de consacrer du temps à la réplication des fichiers d'en-tête C++ en C #.


De plus, il y a des raisons techniques de ne pas le faire ... cela rendra votre binaire final plus grand qu'il ne devrait l'être. Je vais répéter ce commentaire de Mark Benningfield:

Les déclarations dans les fichiers d'en-tête C++ ne finissent pas par faire partie du binaire généré. Ils sont là pour le compilateur et l'éditeur de liens. Ces classes abstraites C # feraient partie du code généré, sans aucun avantage.


Aussi, mentionné par Robert Harvey, techniquement, l'équivalent le plus proche d'un en-tête en C # serait une interface, pas une classe abstraite.

44
TheCatWhisperer

Tout d'abord, comprenez qu'une classe purement abstraite n'est vraiment qu'une interface qui ne peut pas faire d'héritage multiple.

Écrire une classe, extraire l'interface, est une activité morte du cerveau. Tant et si bien que nous avons un refactoring pour cela. C'est dommage. En suivant ce modèle "chaque classe obtient une interface", non seulement cela produit du désordre, mais il manque complètement le point.

Une interface ne doit pas être considérée comme une simple reformulation formelle de tout ce que la classe peut faire. Une interface doit être considérée comme un contrat imposé par le code client utilisateur détaillant ses besoins.

Je n'ai aucun problème à écrire une interface qui n'a actuellement qu'une seule classe l'implémentant. En fait, je m'en fiche si aucune classe ne l'implémente encore. Parce que je pense à ce dont mon code a besoin. L'interface exprime ce que demande le code utilisateur. Tout ce qui arrivera plus tard peut faire ce qu'il veut tant qu'il répond à ces attentes.

Maintenant, je ne fais pas cela chaque fois qu'un objet en utilise un autre. Je le fais lors du franchissement d'une frontière. Je le fais quand je ne veux pas qu'un objet sache exactement à quel autre objet il parle. C'est la seule façon dont le polymorphisme fonctionnera. Je le fais quand je m'attends à ce que l'objet dont parle mon code client soit susceptible de changer. Je ne fais certainement pas cela lorsque j'utilise la classe String. La classe String est sympa et stable et je ne ressens pas le besoin de m'en prémunir.

Lorsque vous décidez d'interagir directement avec une implémentation concrète plutôt que par le biais d'une abstraction, vous prédisez que l'implémentation est suffisamment stable pour ne pas changer.

C'est juste là que je tempère le Dependency Inversion Principle . Vous ne devriez pas appliquer aveuglément et fanatiquement ceci à tout. Lorsque vous ajoutez une abstraction, vous dites vraiment que vous ne faites pas confiance au choix d'implémenter la classe pour être stable pendant la durée de vie du projet.

Tout cela suppose que vous essayez de suivre le Open Closed Principle . Ce principe n'est important que lorsque les coûts associés à la modification directe du code établi sont importants. L'une des principales raisons pour lesquelles les gens ne sont pas d'accord sur l'importance du découplage des objets est que tout le monde ne subit pas les mêmes coûts lors des changements directs. Si retester, recompiler et redistribuer l'intégralité de votre base de code est trivial pour vous, résoudre un besoin de changement avec une modification directe est probablement une simplification très intéressante de ce problème.

Il n'y a tout simplement pas de réponse cérébrale à cette question. Une interface ou une classe abstraite n'est pas quelque chose que vous devez ajouter à chaque classe et vous ne pouvez pas simplement compter le nombre de classes d'implémentation et décider qu'elle n'est pas nécessaire. Il s'agit de gérer le changement. Ce qui signifie que vous anticipez l'avenir. Ne soyez pas surpris si vous vous trompez. Restez simple comme vous pouvez sans vous reculer dans un coin.

Alors s'il vous plaît, n'écrivez pas d'abstractions juste pour nous aider à lire le code. Nous avons des outils pour ça. Utilisez des abstractions pour découpler ce qui doit être découplé.

14
candied_orange

Oui, ce serait terrible parce que (1) Il introduit du code inutile (2) Il confondra le lecteur.

Si vous voulez programmer en C #, vous devez simplement vous habituer à lire C #. Le code écrit par d'autres personnes ne suivra pas ce modèle de toute façon.

5
JacquesB

Tests unitaires

Je vous suggère fortement d'écrire des tests unitaires au lieu d'encombrer le code avec des interfaces ou des classes abstraites (sauf lorsque cela est justifié pour d'autres raisons).

Un test unitaire bien écrit décrit non seulement l'interface de votre classe (comme le ferait un fichier d'en-tête, une classe abstraite ou une interface), mais il décrit également, par exemple, la fonctionnalité souhaitée.

Exemple: où vous pourriez avoir écrit un fichier d'en-tête myclass.h comme ceci:

class MyClass
{
public:
  void foo();
};

À la place, en c #, écrivez un test comme celui-ci:

[TestClass]
public class MyClassTests
{
    [TestMethod]
    public void MyClass_should_have_method_Foo()
    {
        //Arrange
        var myClass = new MyClass();
        //Act
        myClass.Foo();
        //Verify
        Assert.Inconclusive("TODO: Write a more detailed test");
    }
}

Ce test très simple transmet les mêmes informations que le fichier d'en-tête. (Nous devrions avoir une classe nommée "MyClass" avec une fonction sans paramètre "Foo") Alors qu'un fichier d'en-tête est plus compact, le test contient beaucoup plus d'informations.

Une mise en garde: un tel processus consistant à demander à un ingénieur logiciel senior de fournir (échouer) des tests pour que d'autres développeurs résolvent violemment les conflits avec des méthodologies comme TDD, mais dans votre cas, ce serait une énorme amélioration.

1
Guran