J'ai une méthode qui crée un fichier de données après avoir parlé à une carte numérique:
CreateDataFile(IFileAccess boardFileAccess, IMeasurer boardMeasurer)
Ici boardFileAccess
et boardMeasurer
sont la même instance d'un Board
objet qui implémente à la fois IFileAccess
et IMeasurer
. IMeasurer
est utilisé dans ce cas pour une seule méthode qui mettra une broche sur la carte active pour effectuer une mesure simple. Les données de cette mesure sont ensuite stockées localement sur la carte à l'aide IFileAccess
. Board
est situé dans un projet distinct.
Je suis arrivé à la conclusion que CreateDataFile
faire une chose en faisant une mesure rapide puis en stockant les données, et faire les deux dans la même méthode est plus intuitif pour quelqu'un d'autre utilisant ce code, puis devant faire une mesure et écrire dans un fichier comme appels de méthode séparés.
Il me semble gênant de passer deux fois le même objet à une méthode. J'ai envisagé de créer une interface locale IDataFileCreator
qui va s'étendre IFileAccess
, IMeasurer
puis avoir une implémentation contenant une Board
instance qui appellera simplement les Board
méthodes requises . Étant donné que le même objet de carte serait toujours utilisé pour la mesure et l'écriture de fichiers, est-ce une mauvaise pratique de passer deux fois le même objet à une méthode? Dans l'affirmative, l'utilisation d'une interface locale et la mise en œuvre est-elle une solution appropriée?
la source
Réponses:
Non, c'est parfaitement bien. Cela signifie simplement que l'API est sur-conçue par rapport à votre application actuelle .
Mais cela ne prouve pas qu'il n'y aura jamais de cas d'utilisation dans lequel la source de données et le mesureur sont différents. Le but d'une API est d'offrir au programmeur d'applications des possibilités qui ne seront pas toutes utilisées. Vous ne devez pas restreindre artificiellement ce que les utilisateurs d'API peuvent faire à moins que cela ne complique l'API afin que la compréhensibilité nette diminue.
la source
Je suis d'accord avec la réponse de @ KilianFoth que c'est parfaitement bien.
Néanmoins, si vous le souhaitez, vous pouvez créer une méthode qui prend un seul objet qui implémente les deux interfaces:
Il n'y a aucune raison générale pour laquelle les arguments doivent être des objets différents, et si une méthode nécessitait des arguments différents, ce serait une exigence particulière que son contrat devrait clarifier.
la source
Je pense que c'est votre problème, en fait. La méthode ne fait rien. Il exécute deux opérations distinctes qui impliquent des E / S vers différents appareils , toutes deux déchargées sur d'autres objets:
Il s'agit de deux opérations d'E / S différentes. En particulier, le premier ne modifie en rien le système de fichiers.
En fait, nous devons noter qu'il y a une étape intermédiaire implicite:
Votre API doit fournir chacun de ces éléments séparément sous une forme ou une autre. Comment savez-vous qu'un appelant ne voudra pas prendre une mesure sans la stocker n'importe où? Comment savez-vous qu'ils ne voudront pas obtenir une mesure d'une autre source? Comment savez-vous qu'ils ne voudront pas le stocker ailleurs que sur l'appareil? Il y a de bonnes raisons de découpler les opérations. A un strict minimum, chaque pièce doit être disponible pour tous les appels. Je ne devrais pas être obligé d'écrire la mesure dans un fichier si mon cas d'utilisation ne l'exige pas.
Par exemple, vous pouvez séparer les opérations comme celle-ci.
IMeasurer
a un moyen de récupérer la mesure:Votre type de mesure peut être quelque chose de simple, comme un
string
oudecimal
. Je n'insiste pas pour que vous ayez besoin d'une interface ou d'une classe, mais cela rend l'exemple ici plus général.IFileAccess
a une méthode pour enregistrer des fichiers:Ensuite, vous avez besoin d'un moyen de sérialiser une mesure. Construisez cela dans la classe ou l'interface représentant une mesure, ou utilisez une méthode utilitaire:
On ne sait pas encore si cette opération de sérialisation est encore séparée.
Ce type de séparation améliore votre API. Il permet à l' appelant de décider de ce dont il a besoin et quand, plutôt que de forcer vos idées préconçues sur les E / S à effectuer. Les appelants doivent avoir le contrôle pour effectuer toute opération valide , que vous pensiez que c'est utile ou non.
Une fois que vous avez des implémentations distinctes pour chaque opération, votre
CreateDataFile
méthode devient simplement un raccourci pourNotamment, votre méthode ajoute très peu de valeur une fois que vous avez fait tout cela. La ligne de code ci-dessus n'est pas difficile à utiliser directement par vos appelants, et votre méthode est purement pratique tout au plus. Cela devrait être et est quelque chose de facultatif . Et c'est la bonne façon pour l'API de se comporter.
Une fois que toutes les parties pertinentes ont été prises en compte et que nous avons reconnu que la méthode n'est qu'une commodité, nous devons reformuler votre question:
Quel serait le cas d'utilisation le plus courant pour vos appelants?
Si le but est de rendre le cas d'utilisation typique de mesure et d'écriture sur le même tableau un peu plus pratique, il est parfaitement logique de le rendre disponible
Board
directement sur la classe:Si cela n'améliore pas la commodité, je ne me soucierais pas du tout de la méthode.
Cette méthode pratique soulève une autre question.
L'
IFileAccess
interface doit-elle connaître le type de mesure et comment le sérialiser? Si oui, vous pouvez ajouter une méthode pourIFileAccess
:Maintenant, les appelants font juste ceci:
qui est tout aussi court et probablement plus clair que votre méthode de commodité telle que conçue dans la question.
la source
Le client consommateur ne devrait pas avoir à traiter avec une paire d'articles lorsqu'un seul article suffit. Dans votre cas, ils ne le font presque pas, jusqu'à l'invocation de
CreateDataFile
.La solution potentielle que vous proposez est de créer une interface dérivée combinée. Cependant, cette approche nécessite un objet unique qui implémente les deux interfaces, ce qui est plutôt contraignant, sans doute une abstraction qui fuit en ce qu'il est essentiellement personnalisé pour une implémentation particulière. Considérez à quel point ce serait compliqué si quelqu'un voulait implémenter les deux interfaces dans des objets distincts: il faudrait qu'il proxy toutes les méthodes dans l'une des interfaces afin de transmettre à l'autre objet. (FWIW, une autre option consiste à simplement fusionner les interfaces plutôt que d'exiger qu'un objet doive implémenter deux interfaces via une interface dérivée.)
Cependant, une autre approche qui est moins contraignante / dictée pour l'implémentation est celle qui
IFileAccess
est associée à uneIMeasurer
composition in, de sorte que l'une d'entre elles est liée à et référence l'autre. (Cela augmente quelque peu l'abstraction de l'un d'entre eux, car il représente désormais également l'appariement.) IlCreateDataFile
ne pourrait alors prendre qu'une seule des références, par exempleIFileAccess
, et obtenir l'autre au besoin. Votre implémentation actuelle en tant qu'objet qui implémente les deux interfaces serait simplementreturn this;
pour la référence de composition, ici le getter pourIMeasurer
inIFileAccess
.Si le couplage s'avère faux à un moment donné du développement, c'est-à-dire que parfois un mesureur différent est utilisé avec le même accès aux fichiers, vous pouvez faire ce même couplage mais à un niveau supérieur à la place, ce qui signifie que l'interface supplémentaire introduite le ferait. ne pas être une interface dérivée, mais plutôt une interface qui a deux getters, associant un accès à un fichier et un mesureur ensemble via la composition plutôt que la dérivation. Le client consommateur a alors un élément dont il doit s'occuper aussi longtemps que l'appariement est maintenu, et des objets individuels à traiter (pour composer de nouveaux appariements) si nécessaire.
Sur une autre note, je pourrais demander à qui appartient
CreateDataFile
, et la question va à qui est ce tiers. Nous avons déjà quelques clients qui consomment invoqueCreateDataFile
, l'objet / classe propriétaire deCreateDataFile
, etIFileAccess
etIMeasurer
. Parfois, lorsque nous adoptons une vue plus large du contexte, des organisations alternatives, parfois meilleures, peuvent apparaître. Difficile à faire ici car le contexte est incomplet, donc juste matière à réflexion.la source
Certains ont évoqué que cela
CreateDataFile
fait trop. Je pourrais suggérer que celaBoard
fait trop, car l'accès à un fichier semble être une préoccupation distincte du reste du forum.Cependant, si nous supposons que ce n'est pas une erreur, le plus gros problème est que l'interface doit être définie par le client, dans ce cas
CreateDataFile
.Le principe de séparation des interfaces stipule que le client ne devrait pas avoir à dépendre d'une interface plus importante que ce dont il a besoin. En empruntant la phrase de cette autre réponse , cela peut être paraphrasé comme "une interface est définie par ce dont le client a besoin".
Maintenant, il est possible de composer cette interface spécifique au client en utilisant
IFileAccess
etIMeasurer
comme le suggèrent d'autres réponses, mais en fin de compte, ce client devrait avoir une interface sur mesure.la source