Je reviens à cette question tous les deux ans, alors maintenant j'ai décidé de la résoudre une fois pour toutes, en posant ici.
Ainsi, la séquence:
Alors, quel est un flux dans mon approche actuelle? Comment le résoudre de manière "moderne et appropriée OOP architecture")?
Faire d'OperationsService une classe interne de StorageService? Cela signifierait que tout le code serait stocké dans le même fichier (à peu près une violation de SOLID).
Faire d'OperationsService une classe interne de StorageService et le rendre partiel et passer à un autre fichier? Semble un peu effrayant.
En général, je ne veux même pas que OperationsService soit exposé (pas même pour l'enregistrer dans le conteneur IoC) pour le reste de l'application - juste pour faire le gros du travail. OperationsService ne serait pas si grand, donc je ne veux pas en faire un troisième (une sorte de service wrapper) également.
En termes d'objets, je peux reformuler le problème comme "l'objet A a un champ privé, qui ne devrait être accessible que pour l'objet B. La responsabilité A de l'objet est IO, la responsabilité B de l'objet est une logique, donc ils ne peuvent pas être fusionnés".
Pour résumer, nous avons:
Une façon de privilégier OperationsService
sur le reste du système en ce qui concerne des données spécifiques est de lui transmettre directement ces données en cas de besoin: vous pouvez conserver votre logique de manipulation de configuration dans OperationsService
et ajouter une méthode publique AcceptOperations()
dans StorageService
qui prend un objet OperationsService
comme argument et fait appel à sa logique de manipulation de configuration pertinente, lui transmettant directement toutes les données de configuration pertinentes privées .
Concrètement, vous pouvez toujours avoir GetConfigPropertyByNameAndDoSomeMagicWithIt()
être membre de OperationsService
, qui appellera AcceptOperations()
sur l'instance StorageService
appropriée et passera this
comme argument. AcceptOperations()
elle-même appelle une méthode concrète ManipulateConfig()
sur l'instance OperationsService
qui lui est donnée en paramètre, en passant les données de configuration internes à manipuler comme arguments pour ManipulateConfig()
.
Cette proposition ressemble en partie au classique OOP modèle de visiteur , à l'exclusion du double envoi idiome mais en gardant le concept de définir de manière restrictive qui peut visiter quoi et certainement en accord avec l'idée originale derrière le modèle qui consiste à effectuer une telle séparation des responsabilités telle que "... [it offre] la possibilité d'ajouter de nouvelles opérations aux structures d'objets existantes sans modifier les structures. " L'idée d'un tel modèle est d'aider à suivre le principe ouvert-fermé qui est l'un des principes SOLID, dont vous avez mentionné que vous teniez pour commencer.
Vous pouvez mettre les classes que vous voulez être amicales les unes avec les autres dans une assemblée séparée. Ensuite, déclarez uniquement ceux que vous souhaitez être disponibles à l'extérieur de l'Assemblée sous la forme public. Ceux que vous voulez cacher aux classes des autres assemblys que vous déclarez interne. Cela devrait vous donner à peu près ce que vous voulez.
D'un point de vue OOP c'est mieux que ami car ami permet l'accès au-delà des membres publics, ce qui est intrinsèquement contre-OOP.
Les objets sont censés gérer leur état interne et devraient se modifier eux-mêmes si nécessaire. Je pense que vous ne vous avez pas extrait correctement les objets. Storage Service
Est vraiment un gestionnaire de persistance et il ne devrait pas contenir de valeurs de configuration comme valeurs internes. Le service de stockage doit simplement gérer la traduction du fichier vers l'objet et vice versa. alors vous pouvez soit avoir un objet config qui a toutes les méthodes qui font des trucs pour configurer des valeurs ou les exposer, soit une classe de gestionnaire de config qui accepte un objet config à la création et expose les méthodes d'accès nécessaires.
La raison pour laquelle vous rencontrez des problèmes est que Storage Service
Enfreint actuellement le principe de la responsabilité unique. Cela devient plus apparent lorsque vous essayez de le casser davantage avec GetConfigPropertyByNameAndDoSomeMagicWithIt()
. La solution appropriée dans un tel cas serait de refactoriser des objets aux responsabilités mieux définies. friend est une solution de pansement pour une mauvaise conception.
Je pense que ce qui vous manque, c'est IConfigurationService dont une version concrète dépend de IConfigStorageService. Vous pouvez avoir LocalConfigStorageService, qui récupère la configuration à partir d'un fichier ... ou S3ConfigStorageService qui récupère à partir d'un compartiment cloud. Ou même DatabaseConfigStorageService qui le récupère dans une base de données. Dans les trois cas, IConfigStorageService est responsable de la récupération d'une configuration et du stockage de la mise à jour dans la configuration pour IConfigurationService.
IConfigurationService utilise la configuration renvoyée pour répondre aux demandes des consommateurs.
Ceci est spécifiquement une instance de Bridge Pattern