web-dev-qa-db-fra.com

C # n'a pas de classe d'amis - quelles sont les meilleures options

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:

  1. J'écris une application simple qui analyse le fichier Json (configuration vers une application tierce, nommée en outre config) et permet à l'utilisateur de faire quelques modifications avec. J'ai choisi une plate-forme, créé une interface utilisateur de base, lié certains boutons à NewFileCommand, OpenFileCommand, SaveFileCommand, etc.
  2. Maintenant, je dois stocker quelque part la configuration ouverte (et désérialisée dans une classe), j'ai donc créé une nouvelle classe StorageService. Il a quelques méthodes comme OpenFile (), ReloadFile (), SaveFile (), etc. Elles sont appelées à partir du ViewModel, le fichier ouvre StorageService, lit la configuration et la stocke en tant que champ . Au besoin, il l'enregistre dans un fichier - jusqu'ici tout va bien.
  3. En ce moment, je veux ajouter quelques manipulations avec la config elle-même. Je ne veux vraiment pas le mettre dans le StorageService. Certaines méthodes telles que GetConfigPropertyByNameAndDoSomeMagicWithIt () doivent définitivement être placées dans un autre service.
  4. J'ai donc fait un autre service, OperationsService. Maintenant, je veux qu'il ait un accès à la configuration, stocké dans StorageService et ajoute des méthodes GetConfigPropertyByNameAndDoSomeMagicWithIt (). Je ne veux pas exposer StorageService.config dans l'API publique - car j'ai déjà tous les accesseurs nécessaires. Pour tout le monde, à l'exception d'OperationsService. Ce que je veux à peu près faire, celui qui peut accéder à StorageService.config. Ressemblant à un bon cas d'utilisation pour l'ancien bon concept C++ de la classe "friend", où OperationsService peut être déclaré ami de StorageService et accéder à ses internes.
  5. Je me souviens que j'ai rencontré un problème similaire plus tôt, j'ai donc recherché sur "classe amicale C #", ouvert le premier lien dans l'espoir de trouver une solution moderne - et réalisé que j'ai un sentiment de déjà vu.

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:

  • config, qui est POCO et contient des données désérialisées de Json.
  • StorageService, qui contient OpenFile (), SaveFile (). En outre, il contient l'instance actuelle de la configuration.
  • operationsService théorique, qui contient une certaine logique dans GetConfigPropertyByNameAndDoSomeMagicWithIt (). Pour exécuter cette logique, il doit également avoir accès à la configuration. La question est de savoir comment associer OperationsService et StorageService.
5
Vitalii Vasylenko

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.

2
Geezer

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.

5
Martin Maat

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.

1
Ryathal

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

0
Michael Brown