web-dev-qa-db-fra.com

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

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 ModelOne et ModelTwo, ces classes exécutent un type de fonctionnalité similaire mais ne sont pas liées les unes aux autres. Cependant, j'ai une troisième classe CommonFunc qui contient des fonctionnalités publiques implémentées à la fois dans ModelOne et ModelTwo et a été factorisée selon DRY. Les deux modèles sont instanciés dans la classe ModelMain (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 classe1. 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 avec2:

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 j'ai tendance à avoir un rapport de 1: 1 en raison de mon choix/utilisation d'IoC (et ça 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 ModelMain et les sous-modèles.

L'option 3 fonctionne pour séparer la définition de l'interface de l'implémentation, mais maintenant j'ai ces coupures 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 rappelle les jours C++ des fichiers .h et .cpp. Je sais d'où il vient. Ma compréhension d'Unity semble me mettre dans ce coin, mais je ne suis pas sûr non plus de savoir 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.

18
Peter M

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 est doublement déroutant.

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

5
Frank Hileman

Je prends la deuxième approche, avec quelques dossiers/espaces de noms supplémentaires dans des projets complexes bien sûr. 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 n'ont donc qu'à référencer les projets d'interface, pas les projets d'implémentation. Cela peut maintenir des références faibles et éviter des problèmes de référence circulaire.

1
Bernhard Hiller

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?

S'agit-il de 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.

1
shawnhcorey