不是信息专家和告诉不要问单一责任原则吗?

问题描述 投票:24回答:6

信息专家,Tell-Don't-Ask和SRP通常被一起提及为最佳实践。但我认为他们存在分歧。这就是我所说的。

有利于SRP但违反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); 

有利于Tell-Don't-Ask和Info-Expert但违反SRP的代码:

Customer bob = ...;
// Now Customer is doing more than just representing the domain concept of Customer,
// violates SRP
CustomerDTO dto = bob.toDTO();

请告诉我这些做法如何能够和平共处。

术语的定义,

  • 信息专家:具有操作所需数据的对象应承载操作。
  • 告诉不要问:不要向对象询问数据以便工作;告诉对象做这项工作。
  • 单一责任原则:每个对象应具有狭义的责任。
oop solid-principles single-responsibility-principle tell-dont-ask
6个回答
8
投票

我不认为他们是如此矛盾,因为他们正在强调会导致你痛苦的不同事物。一个是关于构造代码以明确特定职责的位置并减少耦合,另一个是关于减少修改类的原因。

我们每个人都必须每天做出关于如何构建代码以及我们愿意在设计中引入哪些依赖关系的决策。

我们已经建立了许多有用的指导方针,格言和模式,可以帮助我们做出决策。

这些中的每一个都有助于检测我们的设计中可能存在的各种问题。对于您可能正在查看的任何特定问题,某处会有一个甜蜜点。

不同的指导方针确实相互矛盾。只需应用您听过或读过的每一条指导都不会使您的设计更好。

对于您今天看到的具体问题,您需要确定可能导致您疼痛的最重要因素。


5
投票

当你要求对象的状态以告诉对象做某事时,你可以谈论“告诉不要问”。

在你的第一个例子中,TransferObjectFactory.createFrom只是一个转换器。在检查状态后,它不会告诉Customer对象执行某些操作。

我认为第一个例子是正确的。


2
投票

这些课程并不矛盾。 DTO只是用作存储数据的管道,旨在用作哑容器。它当然不违反SRP。

另一方面.toDTO方法值得怀疑 - 客户为什么要承担这个责任?对于“纯粹”的缘故,我会有另一个类,就是从Customer这样的业务对象创建DTO。

不要忘记这些原则是原则,当你可以使用更简单的解决方案,直到改变要求迫使问题,然后这样做。绝对不必要的复杂性是必须避免的。

我强烈推荐,BTW,罗伯特C.马丁的敏捷模式,实践和原则,以更深入地处理这个主题。


1
投票

带有姐妹班级的DTO(就像你一样)违反了你所说的所有三个原则和封装,这就是你在这里遇到问题的原因。

您使用此CustomerDTO的原因是什么?为什么您不能简单地使用客户,并在客户中包含DTO数据?如果您不小心,CustomerDTO将需要客户,客户将需要CustomerDTO。

TellDontAsk说,如果您基于一个对象(例如客户)的状态做出决定,那么该决定应该在客户类本身内部执行。

例如,如果您想提醒客户支付任何未结账单,请致电

  List<Bill> bills = Customer.GetOutstandingBills();
  PaymentReminder.RemindCustomer(customer, bills);

这是违规行为。相反,你想做

Customer.RemindAboutOutstandingBills() 

(当然,您需要传递PaymentReminder作为对客户构建的依赖)。

Information Expert也说了同样的话。

单一责任原则很容易被误解 - 它说客户类应该有一个责任,而且分组数据,方法和其他类与“客户”概念一致的责任应该只由一个类封装。什么构成单一责任是非常难以准确定义的,我建议更多地阅读这个问题。


1
投票

Craig Larman在介绍GRASP将UML和模式应用于面向对象的分析和设计与迭代开发(2004)时讨论了这个问题:

在某些情况下,Expert提出的解决方案是不可取的,通常是因为耦合和内聚的问题(这些原则将在本章后面讨论)。

例如,谁应该负责将销售保存在数据库中?当然,要保存的大部分信息都在Sale对象中,因此Expert可以说责任在于Sale类。并且,通过该决定的逻辑扩展,每个类都有自己的服务来将自己保存在数据库中。但是按照这种推理行事会导致内聚,耦合和重复方面的问题。例如,Sale类现在必须包含与数据库处理相关的逻辑,例如与SQL和JDBC(Java数据库连接)相关的逻辑。该课程不再只关注“作为销售”的纯粹应用逻辑。现在,其他类型的责任降低了它的凝聚力。该类必须耦合到另一个子系统的技术数据库服务,例如JDBC服务,而不是仅仅耦合到软件对象的域层中的其他对象,因此它的耦合增加。并且很可能在许多持久化类中会复制类似的数据库逻辑。

所有这些问题表明违反了基本的架构原则:设计主要系统问题的分离。将应用程序逻辑保存在一个地方(例如域软件对象),将数据库逻辑保存在另一个地方(例如单独的持久性服务子系统),等等,而不是在同一组件中混合不同的系统问题。[11]

支持主要关注点的分离改善了设计中的耦合和内聚。因此,尽管通过Expert我们可以找到一些理由将数据库服务的责任放在Sale类中,但由于其他原因(通常是内聚和耦合),我们最终会得到一个糟糕的设计。

因此,SRP通常胜过信息专家。

但是,依赖倒置原则可以很好地与Expert结合。这里的论点是客户不应该具有CustomerDTO的依赖性(一般到细节),反之亦然。这意味着CustomerDTO是专家,应该知道如何在给定客户的情况下构建自己:

CustomerDTO dto = new CustomerDTO(bob);

如果你对新的过敏,你可能会变得静止:

CustomerDTO dto = CustomerDTO.buildFor(bob);

或者,如果你讨厌两者,我们回到一个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);
    }
}

0
投票

我不是100%同意/你的两个例子是具有代表性的,但从一般的角度来看,你似乎是假设两个对象和两个对象。

如果你将问题进一步分开并创建一个(或多个)专用对象来承担你所拥有的各个职责,然后让控制对象将其正在使用的其他对象的实例传递给你已经分割出来的专用对象,应该能够观察到SRP之间的快乐妥协(每个责任由专门的对象处理)和Tell Do Not Ask(控制对象告诉它正在组合的专门对象做他们做的任何事情,到彼此)。

它是一个组合解决方案,依赖于某种控制器来协调和委托其他对象,而不会陷入其内部细节。

© www.soinside.com 2019 - 2024. All rights reserved.