Est-ce que L'information N'est pas une question D'Expert / Tell Don't Ask en contradiction avec le principe de Responsabilité Unique?
c'est probablement juste moi, c'est pourquoi je pose la question. Expert en Information, Tell Don't Ask et SRP sont souvent cités ensemble comme des pratiques exemplaires. Mais je pense qu'ils sont en désaccord. Voilà de quoi je parle:
Code qui favorise SRP mais viole Tell Don't Ask, Info Expert:
Customer bob = ...;
// TransferObjectFactory has to use Customer's accessors to do its work,
// violates Tell Don't Ask
CustomerDTO dto = TransferObjectFactory.createFrom(bob);
Code qui favorise Tell Don't Ask / Info Expert mais viole SRP:
Customer bob = ...;
// Now Customer is doing more than just representing the domain concept of Customer,
// violates SRP
CustomerDTO dto = bob.toDTO();
S'ils sont vraiment en désaccord, c'est une justification de mon trouble obsessionnel-compulsif. Sinon, expliquez-moi comment ces pratiques peuvent coexister pacifiquement. Remercier.
Edit: quelqu'un veut une définition des termes -
expert en Information: les objets qui ont les données nécessaires pour l'opération doivent héberger l'opération
Dites Ne pas Poser la question: ne posez pas d'objets de données dans le but de faire du travail; dire les objets pour faire le travail
Responsabilité Unique Principe: chaque objet doit avoir une responsabilité étroitement définie
6 réponses
Je ne pense pas qu'ils soient autant en désaccord qu'ils mettent l'accent sur différentes choses qui vous causeront de la douleur. L'une consiste à structurer le code pour préciser où se situent les responsabilités particulières et réduire l'accouplement, l'autre consiste à réduire les raisons de modifier une classe.
nous devons tous prendre des décisions chaque jour sur la façon de structurer le code et quelles sont les dépendances que nous sommes prêts à introduire dans les conceptions.
nous avons construit beaucoup de lignes directrices utiles, des maximes et des motifs qui peuvent nous aider à prendre les décisions.
chacun de ceux-ci est utile pour détecter différents types de problèmes qui pourraient être présents dans nos conceptions. Pour tout problème spécifique que vous pouvez être en train de regarder il y aura un endroit doux quelque part.
les différentes lignes directrices se contredisent. Il suffit d'appliquer chaque conseil que vous avez entendu ou Lu ne rendra pas votre conception meilleure.
pour le problème spécifique que vous regardez aujourd'hui, vous devez décider quels sont les facteurs les plus importants qui sont susceptibles de vous causer de la douleur.
vous pouvez parler de" Tell Don't Ask " quand vous demandez l'état de l'objet afin de dire à l'objet de faire quelque chose.
dans votre premier exemple TransferObjectFactory.createf from just a converter. Il ne dit pas à L'objet du client de faire quelque chose après avoir inspecté son état.
je pense que le premier exemple est correct.
ces classes ne sont pas en désaccord. Le DTO sert simplement de conduit de données de stockage qui est destiné à être utilisé comme un conteneur muet. Il ne viole certainement pas le SRP.
d'autre part le .la méthode toDTO est discutable -- pourquoi le client devrait-il avoir cette responsabilité? Pour l'amour de "pureté" j'aurais une autre classe qui le travail c'était de créer des Dto à partir d'objets commerciaux comme client.
N'oubliez pas ces les principes sont des principes, et lorsque vous pouvez éliminer des solutions plus simples jusqu'à ce que l'évolution des exigences force la question, faites-le. La complexité inutile est certainement quelque chose à éviter.
je recommande fortement, d'ailleurs, Robert C. Martin Agile Habitudes, les Pratiques et les principes pour beaucoup plus en profondeur les traitements de ce sujet.
DTOs avec une classe sœur (comme vous avez) violer les trois principes que vous avez déclaré, et encapsulation, ce qui est pourquoi vous avez des problèmes ici.
pour quoi utilisez-vous ce CustomerDTO, et pourquoi ne pouvez-vous pas simplement utiliser le client, et avoir les données DTOs à l'intérieur du client? Si vous n'êtes pas prudent, le CustomerDTO aura besoin d'un client, et un client aura besoin d'un CustomerDTO.
TellDontAsk dit que si vous basez une décision sur la l'état d'un objet (par exemple un client), alors cette décision devrait être effectuée à l'intérieur de la classe de client elle-même.
un exemple est si vous voulez rappeler au client de payer les factures en souffrance, donc vous appelez
List<Bill> bills = Customer.GetOutstandingBills();
PaymentReminder.RemindCustomer(customer, bills);
c'est une violation. Au lieu de cela, vous voulez faire
Customer.RemindAboutOutstandingBills()
(et bien sûr, vous aurez besoin de passer dans le PaymentReminder comme une dépendance à la construction du client).
L'expert en Information dit à peu près la même chose.
le principe de responsabilité unique peut facilement être mal compris - il est dit que la classe de clients devrait avoir une responsabilité, mais aussi que la responsabilité de grouper des données, des méthodes et d'autres classes alignées sur le concept de "client" devrait être encapsulé par une seule classe. Ce qui constitue une responsabilité unique est extrêmement difficile à définir avec précision et je recommande une lecture plus approfondie de la question.
Craig Larman en a discuté lorsqu'il a introduit le GRASP dans L'application de UML et de Patterns à L'analyse orientée objet et à la conception et au développement itératif (2004):
dans certaines situations, une solution proposée par Expert n'est pas souhaitable, généralement en raison de problèmes de couplage et de cohésion (ces principes sont discutés plus loin dans ce chapitre).
par exemple, qui devrait être responsable de sauvegarder une vente dans une base de données? Certes, une grande partie de l'information à sauvegarder se trouve dans l'objet de la vente, et donc Expert pourrait faire valoir que la responsabilité réside dans la classe de Vente. Et, par extension logique de cette décision, chaque classe aurait ses propres services pour se sauver dans une base de données. Mais agir sur ce raisonnement conduit à des problèmes de cohésion, de couplage et de duplication. Par exemple, la classe Sale doit maintenant contenir de la logique liée au traitement de base de données, comme celle liée à SQL et JDBC (Java Database Connectivity). Le class ne se concentre plus uniquement sur la logique d'application pure d'être une vente."Aujourd'hui, d'autres types de responsabilités réduisent sa cohésion. La classe doit être couplée aux services de base de données techniques d'un autre sous-système, tels que les services JDBC, plutôt que d'être simplement couplée à d'autres objets dans la couche domaine des objets logiciels, de sorte que son couplage augmente. Et il est probable qu'une logique de base de données similaire serait dupliquée dans de nombreuses classes persistantes.
tous ces problèmes indique une violation d'un principe architectural de base: la conception d'une séparation des principales préoccupations du système. Conserver la logique de l'application à un endroit (comme les objets logiciels du domaine), conserver la logique de la base de données à un autre endroit (comme un sous-système distinct de services de persistance), et ainsi de suite, plutôt que de mêler des préoccupations de système différentes dans le même composant.[11]
supporter une séparation de préoccupations majeures améliore l'assemblage et la cohésion dans une conception. Ainsi, même si l' par Expert nous pourrions trouver une certaine justification pour mettre la responsabilité des services de base de données dans la classe de vente, pour d'autres raisons (généralement la cohésion et le couplage), nous finirions par avoir un mauvais design.
ainsi, le SRP l'emporte généralement sur L'expert en Information.
cependant, le principe D'Inversion de dépendance peut bien se combiner avec Expert. L'argument ici serait que le client ne devrait pas avoir une dépendance de CustomerDTO (général à détail), mais dans l'autre sens. Cela signifierait que CustomerDTO est L'Expert et devrait savoir comment se construire compte tenu D'un client:
CustomerDTO dto = new CustomerDTO(bob);
si vous êtes allergique à nouveau, vous pouvez devenir statique:
CustomerDTO dto = CustomerDTO.buildFor(bob);
Ou, si vous détestez les deux, nous reviendrons autour d'un AbstractFactory:
public abstract class DTOFactory<D, E> {
public abstract D createDTO(E entity);
}
public class CustomerDTOFactory extends DTOFactory<CustomerDTO, Customer> {
@Override
public CustomerDTO createDTO(Customer entity) {
return new CustomerDTO(entity);
}
}
Je ne suis pas d'accord à 100% avec vos deux exemples comme étant représentatifs, mais d'un point de vue général vous semblez raisonner à partir de l'hypothèse de deux objets et seulement deux objets.
si vous séparez le problème plus loin et créez un (ou plusieurs) objet (s) spécialisé (s) pour assumer les responsabilités individuelles que vous avez, et puis avoir les instances de contrôle de l'objet passer des autres objets qu'il utilise aux objets spécialisés que vous avez découpé, vous devrait être capable d'observer un compromis heureux entre SRP (chaque responsabilité a traité par un objet spécialisé), et Tell Don't Ask (l'objet de contrôle dit aux objets spécialisés qu'il compose ensemble de faire ce qu'ils font, l'un à l'autre).
c'est une solution de composition qui s'appuie sur une sorte de controller pour coordonner et déléguer entre d'autres objets sans s'embourber dans leurs détails internes.