Comment organiser au mieux les fichiers de classe et d'interface?

17

OK .. après toute la discussion, je modifie légèrement ma question pour mieux refléter un exemple concret que je traite.


J'ai deux classes ModelOneet ModelTwo, ces classes exécutent un type de fonctionnalité similaire mais ne sont pas liées l'une à l'autre. Cependant , j'ai une troisième classe CommonFuncqui contient certaines fonctionnalités du public qui est mis en œuvre à la fois ModelOneet ModelTwoet a été pris en compte comme par DRY. Les deux modèles sont instanciés au sein de la ModelMainclasse (qui elle-même est instanciée à un niveau supérieur, etc. - mais je m'arrête à ce niveau).

Le conteneur IoC que j'utilise est Microsoft Unity . Je ne prétends pas être un expert en la matière, mais d'après ce que je comprends, vous enregistrez un tuple d'interface et de classe avec le conteneur et lorsque vous voulez une classe concrète, vous demandez au conteneur IoC quel que soit l'objet correspondant à une interface spécifique. Cela implique que pour chaque objet que je veux instancier depuis Unity, il doit y avoir une interface correspondante. Étant donné que chacune de mes classes exécute des fonctionnalités différentes (et sans chevauchement), cela signifie qu'il existe un rapport 1: 1 entre l'interface et la classe 1 . Cependant, cela ne signifie pas que j'écris servilement une interface pour chaque classe que j'écris.

Ainsi, au niveau du code, je me retrouve avec 2 :

public interface ICommonFunc 
{ 
}

public interface IModelOne 
{ 
   ICommonFunc Common { get; } 
   .. 
}

public interface IModelTwo
{ 
   ICommonFunc Common { get; } 
   .. 
}

public interface IModelMain 
{ 
  IModelOne One { get; } 
  IModelTwo Two { get; } 
  ..
}

public class CommonFunc : ICommonFunc { .. }

public class ModelOne : IModelOne { .. }

public class ModelTwo : IModelTwo { .. }

public class ModelMain : IModelMain { .. }

La question est de savoir comment organiser ma solution. Dois-je garder la classe et l'interface ensemble? Ou dois-je garder les classes et les interfaces ensemble? PAR EXEMPLE:

Option 1 - Organisé par nom de classe

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-Main
          |   |
          |   |-IModelMain.cs
          |   |-ModelMain.cs
          |
          |-One
          |   |
          |   |-IModelOne.cs
          |   |-ModelOne.cs
          |
          |-Two
              |
              |-IModelTwo.cs
              |-ModelTwo.cs
              |

Option 2 - Organisé par fonctionnalité (principalement)

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-IModelMain.cs
          |-IModelOne.cs
          |-IModelTwo.cs
          |-ModelMain.cs
          |-ModelOne.cs
          |-ModelTwo.cs
          |

Option 3 - Interface et mise en œuvre séparées

MySolution
  |
  |-MyProject
      |
      |-Interfaces
      |   |
      |   |-Models
      |   |   |
      |       |-Common
      |       |   |-ICommonFunc.cs
      |       |
      |       |-IModelMain.cs
      |       |-IModelOne.cs
      |       |-IModelTwo.cs
      |
      |-Classes
          | 
          |-Models
          |   |
              |-Common
              |   |-CommonFunc.cs
              |
              |-ModelMain.cs
              |-ModelOne.cs
              |-ModelTwo.cs
              |

Option 4 - Aller plus loin dans l'exemple de fonctionnalité

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Components
          |   |
          |   |-Common
          |   |   |
          |   |   |-CommonFunc.cs
          |   |   |-ICommonFunc.cs
          |   |   
          |   |-IModelOne.cs
          |   |-IModelTwo.cs
          |   |-ModelOne.cs
          |   |-ModelTwo.cs
          |
          |-IModelMain.cs
          |-ModelMain.cs
          |

Je déteste en quelque sorte l'option 1 à cause du nom de classe dans le chemin. Mais comme je tend à un rapport de 1: 1 en raison de mon choix / utilisation d'IoC (et cela peut être discutable), cela a des avantages à voir la relation entre les fichiers.

L'option 2 me plaît, mais maintenant j'ai brouillé les eaux entre le ModelMainet les sous-modèles.

L'option 3 fonctionne pour séparer la définition de l'interface de l'implémentation, mais j'ai maintenant ces ruptures artificielles dans les noms de chemin.

Option 4. J'ai pris l'option 2 et l'ai modifiée afin de séparer les composants du modèle parent.

Y a-t-il une bonne raison de préférer l'un à l'autre? Ou toute autre disposition potentielle que j'ai manquée?


1. Frank a fait un commentaire selon lequel le rapport 1: 1 ramène aux jours C ++ des fichiers .h et .cpp. Je sais d'où il vient. Ma compréhension de l'Unité semble me mettre dans ce coin, mais je ne sais pas non plus comment m'en sortir si vous suivez également l'adage de Program to an interface Mais c'est une discussion pour un autre jour.

2. J'ai omis les détails de chaque constructeur d'objets. C'est là que le conteneur IoC injecte des objets selon les besoins.

Peter M
la source
1
Pouah. Il sent que vous avez un rapport interface / classe de 1: 1. Quel conteneur IoC utilisez-vous? Ninject fournit des mécanismes qui ne nécessitent pas un rapport 1: 1.
RubberDuck
1
@RubberDuck FWIW J'utilise Unity. Je ne prétends pas être un expert en la matière, mais si mes cours sont bien conçus avec des responsabilités uniques, comment puis-je ne pas me retrouver avec un rapport presque 1: 1?
Peter M
Avez-vous besoin d'IBase ou la base pourrait-elle être abstraite? Pourquoi IDerivedOne a-t-il déjà implémenté IBase? Vous devez dépendre d'IBase, non dérivé. Je ne connais pas Unity, mais d'autres conteneurs IoC vous permettent de faire une injection "contextuelle". Fondamentalement, lorsqu'il a Client1besoin d'un IBase, il fournit un Derived1. En cas de Client2besoin IBase, l'IoC fournit un Derived2.
RubberDuck
1
Eh bien, si la base est abstraite, il n'y a aucune raison d'en avoir une interface. An interfaceest vraiment juste une classe abstraite avec tous les membres virtuels.
RubberDuck
1
RubberDuck est correct. Les interfaces superflues sont simplement ennuyeuses.
Frank Hileman

Réponses:

4

Puisqu'une interface est abstraitement similaire à une classe de base, utilisez la même logique que vous utiliseriez pour une classe de base. Les classes implémentant une interface sont étroitement liées à l'interface.

Je doute que vous préfériez un répertoire appelé "Classes de base"; la plupart des développeurs n'en voudraient pas, ni un répertoire appelé "Interfaces". En c #, les répertoires sont également des espaces de noms par défaut, ce qui crée une confusion double.

La meilleure approche consiste à réfléchir à la façon dont vous diviseriez les classes / interfaces si vous deviez en placer dans une bibliothèque distincte et organiser les espaces de noms / répertoires de manière similaire. Les directives de conception du framework .net contiennent des suggestions d'espace de noms qui peuvent être utiles.

Frank Hileman
la source
Je connais un peu le lien que vous avez fourni, mais je ne sais pas comment il se rapporte à l'organisation des fichiers. Alors que VS utilise par défaut les noms de chemin pour l'espace de noms initial, je sais que c'est un choix arbitraire. J'ai également mis à jour mon "échantillon" de code et mes possibilités de mise en page.
Peter M
@PeterM Je ne sais pas quel IDE vous utilisez, mais Visual Studio utilise les noms de répertoire pour générer automatiquement les déclarations d'espace de noms lors de l'ajout d'une classe, par exemple. Cela signifie que même si vous pouvez avoir des différences entre les noms de répertoire et les noms d'espace de noms, il est plus difficile de travailler de cette façon. Donc, la plupart des gens ne font pas ça.
Frank Hileman
J'utilise VS. Et oui je connais la douleur. Il ramène des souvenirs de la disposition des fichiers Visual Source Safe.
Peter M
1
@PeterM En ce qui concerne le rapport interface / classe 1: 1. Certains outils l'exigent. Je considère cela comme un défaut de l'outil - .net a suffisamment de capacités de réflexion pour ne pas avoir besoin de telles restrictions dans un tel outil.
Frank Hileman
1

Je prends la deuxième approche, avec bien sûr plus de dossiers / espaces de noms dans des projets complexes. Cela signifie que je dissocie les interfaces des implémentations concrètes.

Dans un autre projet, vous devrez peut-être connaître la définition de l'interface, mais il n'est pas du tout nécessaire de connaître une classe concrète l'implémentant - en particulier lorsque vous utilisez un conteneur IoC. Ces autres projets doivent donc uniquement référencer les projets d'interface, pas les projets de mise en œuvre. Cela peut maintenir des références faibles et éviter des problèmes de référence circulaire.

Bernhard Hiller
la source
J'ai révisé mon exemple de code et ajouté quelques options supplémentaires
Peter M
1

Comme toujours pour toute question de conception, la première chose à faire est de déterminer qui sont vos utilisateurs. Comment vont-ils utiliser votre organisation?

Est-ce que ce sont des codeurs qui utiliseront vos cours? Il peut alors être préférable de séparer les interfaces du code.

Sont-ils responsables de votre code? Gardez ensuite les interfaces et les classes ensemble peut être le meilleur.

Asseyez-vous et créez des cas d'utilisation. La meilleure organisation peut alors apparaître devant vous.

shawnhcorey
la source
-2

Je pense qu'il vaut mieux stocker les interfaces dans la bibliothèque séparée. Premièrement, ce type de conception augmente la dépendance. C'est pourquoi l'implémentation de ces interfaces peut se faire par un autre langage de programmation.

Larissa Savchekoo
la source