Je suis assez pragmatique, mais ma principale préoccupation ici est que vous puissiez permettre à cela ConfigBlock
de dominer vos conceptions d'interface de manière potentiellement mauvaise. Lorsque vous avez quelque chose comme ça:
explicit MyGreatClass(const ConfigBlock& config);
... une interface plus appropriée pourrait ressembler à ceci:
MyGreatClass(int foo, float bar, const string& baz);
... par opposition à la simple sélection de ces foo/bar/baz
champs dans un massif ConfigBlock
.
Conception d'interface paresseuse
Sur le plan positif, ce type de conception facilite la conception d'une interface stable pour votre constructeur, par exemple, car si vous finissez par avoir besoin de quelque chose de nouveau, vous pouvez simplement le charger dans un ConfigBlock
(éventuellement sans aucune modification de code), puis cerise- choisissez tout ce dont vous avez besoin sans aucun changement d'interface, seulement un changement dans l'implémentation de MyGreatClass
.
C'est donc à la fois un avantage et un inconvénient que cela vous libère de la conception d'une interface plus réfléchie qui n'accepte que les entrées dont elle a réellement besoin. Il applique la mentalité de «Donnez-moi juste cette masse massive de données, je vais choisir ce dont j'ai besoin» plutôt que quelque chose comme «Ces paramètres précis sont ce dont cette interface a besoin pour fonctionner».
Il y a donc certainement des avantages ici, mais ils pourraient être largement contrebalancés par les inconvénients.
Couplage
Dans ce scénario, toutes ces classes construites à partir d'une ConfigBlock
instance finissent par avoir leurs dépendances comme ceci:
Cela peut devenir un PITA, par exemple, si vous souhaitez effectuer un test unitaire Class2
dans ce diagramme de manière isolée. Vous devrez peut-être simuler superficiellement diverses ConfigBlock
entrées contenant les champs pertinents Class2
pour pouvoir le tester dans diverses conditions.
Dans tout type de nouveau contexte (que ce soit un test unitaire ou un tout nouveau projet), de telles classes peuvent finir par devenir plus un fardeau à (ré) utiliser, car nous finissons par devoir toujours apporter ConfigBlock
avec nous pour le trajet et le configurer en conséquence.
Réutilisation / déployabilité / testabilité
Au lieu de cela, si vous concevez ces interfaces de manière appropriée, nous pouvons les découpler ConfigBlock
et nous retrouver avec quelque chose comme ceci:
Si vous remarquez dans ce diagramme ci-dessus, toutes les classes deviennent indépendantes (leurs couplages afférents / sortants diminuent de 1).
Cela conduit à beaucoup plus de classes indépendantes (au moins indépendantes de ConfigBlock
) qui peuvent être beaucoup plus faciles à (ré) utiliser / tester dans de nouveaux scénarios / projets.
Maintenant, ce Client
code finit par être celui qui doit dépendre de tout et l'assembler tous ensemble. La charge finit par être transférée vers ce code client pour lire les champs appropriés à partir de a ConfigBlock
et les passer dans les classes appropriées en tant que paramètres. Pourtant, ce code client est généralement conçu de manière étroite pour un contexte spécifique, et son potentiel de réutilisation sera généralement zilch ou fermé de toute façon (il pourrait s'agir de la fonction de main
point d'entrée de votre application ou quelque chose comme ça).
Du point de vue de la réutilisabilité et des tests, cela peut aider à rendre ces classes plus indépendantes. Du point de vue de l'interface pour ceux qui utilisent vos classes, cela peut également aider à indiquer explicitement les paramètres dont ils ont besoin au lieu d'un seul massif ConfigBlock
qui modélise tout l'univers des champs de données requis pour tout.
Conclusion
En général, ce type de conception orientée classe qui dépend d'un monolithe qui a tout le nécessaire a tendance à avoir ce genre de caractéristiques. Leur applicabilité, déployabilité, réutilisabilité, testabilité, etc. peuvent en conséquence être considérablement dégradées. Pourtant, ils peuvent en quelque sorte simplifier la conception de l'interface si nous tentons une rotation positive. C'est à vous de mesurer ces avantages et inconvénients et de décider si les compromis en valent la peine. En règle générale, il est beaucoup plus sûr de se tromper contre ce type de conception où vous choisissez parmi un monolithe dans des classes qui sont généralement destinées à modéliser une conception plus générale et plus largement applicable.
Enfin et surtout:
extern CodingBlock MyCodingBlock;
... c'est potentiellement encore pire (plus asymétrique?) en termes de caractéristiques décrites ci-dessus que l'approche d'injection de dépendance, car elle finit par coupler vos classes non seulement à ConfigBlocks
, mais directement à une instance spécifique de celle-ci. Cela dégrade davantage l'applicabilité / la déployabilité / la testabilité.
Mon conseil général serait de préférer la conception d'interfaces qui ne dépendent pas de ces types de monolithes pour fournir leurs paramètres, au moins pour les classes les plus généralement applicables que vous concevez. Et évitez l'approche globale sans injection de dépendance si vous le pouvez, sauf si vous avez vraiment une raison très forte et confiante de ne pas l'éviter.
switch
instruction ou à uneif
instruction testant une valeur lue dans les fichiers de configuration.Oui. Il est préférable de centraliser les constantes et valeurs d'exécution et le code pour les lire.
C'est mauvais: la plupart de vos constructeurs n'auront pas besoin de la plupart des valeurs. Au lieu de cela, créez des interfaces pour tout ce qui n'est pas trivial à construire:
ancien code (votre proposition):
nouveau code:
instancier une MyGreatClass:
Ici,
current_config_block
est une instance de votreConfigBlock
classe (celle qui contient toutes vos valeurs) et laMyGreatClass
classe reçoit uneGreatClassData
instance. En d'autres termes, ne transmettez aux constructeurs que les données dont ils ont besoin et ajoutez des fonctionnalités à votreConfigBlock
pour créer ces données.Ce code suggère que vous disposerez d'une instance globale de CodingBlock. Ne faites pas cela: normalement, vous devriez avoir une instance déclarée globalement, quel que soit le point d'entrée utilisé par votre application (fonction principale, DllMain, etc.) et le passer comme argument partout où vous en avez besoin (mais comme expliqué ci-dessus, vous ne devez pas passer toute la classe, il suffit d'exposer les interfaces autour des données et de les transmettre).
Aussi, ne liez pas vos classes clientes (vos
MyGreatClass
) au type deCodingBlock
; Cela signifie que, si vous prenezMyGreatClass
une chaîne et cinq entiers, vous ferez mieux de passer cette chaîne et ces entiers que vous ne passerez dans aCodingBlock
.la source
Réponse courte:
Vous n'avez pas besoin de tous les paramètres pour chacun des modules / classes de votre code. Si vous le faites, il y a un problème avec votre conception orientée objet. Surtout en cas de test unitaire, définir toutes les variables dont vous n'avez pas besoin et passer cet objet n'aiderait pas à la lecture ou à la maintenance.
la source
ConfigBlock
classe. Le point ici est de ne pas fournir tous, dans ce cas, le contexte de l'état du système, juste particulier, les valeurs requises pour le faire.