2009-08-07 10 views
1

J'ai quelques questions pour vous les personnes sages impliquant la conception OO avec Interfaces et classes de base abstraites. Considérez le scénario suivant:Orientation de l'objet - Où placer cette déclaration de l'interface

J'ai une classe de basse abstraite "DataObjectBase" et une classe dérivée "UserDataObject". J'ai aussi une interface "IDataObject". L'interface expose bien sûr toutes les méthodes et propriétés publiques que mes objets de données doivent exposer, et vous pouvez probablement deviner que la base abstraite implémente les méthodes et propriétés communes à tous les objets de données. Ma question est la suivante: si la classe de basse abstraite DataObjectBase implémente tout ce qui est spécifié dans l'interface IDataObject, l'interface devrait-elle être déclarée sur la classe de base ou sur les classes dérivées?

En C#, les interfaces déclarées sur la classe de base implicitement sont appliquées aux classes dérivées, mais est-ce la meilleure pratique? Il me semble que l'implémentation de l'interface sur la classe de base rend moins évident que la classe dérivée implémente l'interface, mais exige à nouveau que l'interface soit spécifiée pour chaque classe dérivée.

En outre, si la classe de base n'était pas abstraite, la recommandation serait-elle modifiée?

Une deuxième sous-question: Si la classe de base implémente toutes les méthodes/propriétés de l'interface IDataObject, l'interface est-elle nécessaire? Le nom de type de la classe de base peut simplement être utilisé à la place du nom de l'interface, à savoir:

private DataObjectBase _dataObject;
private IDataObject _dataObject;

Dans l'exemple ci-dessus (où la base implémente tout ce qui est exposé par l'interface), les deux types dérivés peuvent être affectés. Personnellement, j'utilise toujours l'interface dans ces situations, mais je suis intéressé à entendre les pensées des gens.

Merci d'avance.

Répondre

1

Ma façon de penser à de tels problèmes est de considérer les différentes personnes lisant le code, les «rôles» si vous voulez. Tenez également compte de la maintenabilité globale du système.

Tout d'abord, du code attend l'utilisation de l'interface. C'est écrit en termes d'interface, l'auteur n'a (devrait avoir) aucun intérêt dans l'implémentation. C'est pourquoi nous fournissons la classe Interface. De ce point de vue, la classe de base abstraite n'est que l'une des nombreuses hiérarchies d'implémentation possibles. Ne dites pas ce rôle sur les détails de mise en œuvre. Gardez l'interface.

Ensuite, nous avons le rôle de concevoir une implémentation. Ils proposent une approche possible et découvrent quelques variations, ils veulent donc rassembler du code commun.Classe de base abstraite - remplissez les choses courantes ici, laissez les exécutants détaillés remplir les lacunes. Aidez-les en fournissant des méthodes abstraites en disant "votre code va ici". Notez que ces méthodes ne doivent pas seulement être celles de l'interface. Notez également que cette classe de base abstraite pourrait même implémenter plus d'une interface! (Par exemple, CleverThingWorker mais aussi un IntermediateWorkPersister.)

Ensuite, nous avons le rôle qui fait réellement la fine implémentation détaillée. Remplissez les lacunes ici. Morte facile à comprendre. Dans ce cas, vous n'avez même pas besoin de considérer l'interface en tant que telle. Votre travail consiste à concrétiser cette classe abstraite.

Ligne de fond ... J'utilise les classes Interfaces et Base. Vous mettez l'interface sur la classe de base. Nous n'ajoutons pas de valeur en l'ajoutant à la classe d'implémentation.

1

Si vos classes d'utilisateur héritent toujours d'une classe de base, vous n'avez pas besoin de l'interface. S'il existe une possibilité que vous ayez des classes qui correspondent à l'interface mais ne sont pas dérivées de la classe de base, utilisez l'interface. En ce qui concerne l'interface cachée dans la classe de base et donc pas immédiatement visible dans la classe d'utilisateurs, ceci est normal et peut être traité par le compilateur. C'est aussi là que les bonnes conventions de dénomination entrent - votre UserDataObject a un nom qui correspond à IDataObject, tout comme DataObjectBase. Vous pouvez ajouter un commentaire au fichier de classe qui dit qu'il hérite de IDataObject, mais il sera visible qu'il hérite de DataObjectBase, qui à son tour semble hériter de IDataObject par son nom.

0

L'autre chose à mentionner est que l'utilisation d'interfaces facilite la mise en œuvre de tests automatisés. Supposons, par exemple, que l'une des méthodes de l'interface est censée déclencher une exception - telle que 'DatabaseConnectionLostException' - et que vous voulez tester le code client pour vérifier qu'il se comporte correctement dans une telle situation.

Il est simple de fournir une implémentation de l'interface qui lève l'exception, permettant d'écrire le test.

Si vous utilisiez la classe de base abstraite à la place de l'interface, cette opération serait plus délicate (OK, vous pouvez utiliser Mocks, mais la solution d'interface est beaucoup plus propre)

Questions connexes