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 l'une à l'autre. Cependant , j'ai une troisième classe CommonFunc
qui contient certaines fonctionnalités du public qui est mis en œuvre à la fois ModelOne
et ModelTwo
et a été pris en compte comme par DRY
. Les deux modèles sont instanciés au sein de la ModelMain
classe (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 ModelMain
et 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.
la source
Client1
besoin d'unIBase
, il fournit unDerived1
. En cas deClient2
besoinIBase
, l'IoC fournit unDerived2
.interface
. Aninterface
est vraiment juste une classe abstraite avec tous les membres virtuels.Réponses:
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.
la source
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.
la source
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.
la source
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.
la source