25

Information-ExpertTell-Don't-AskSRP通常作为最佳实践一起被提及。但我认为他们是矛盾的。这就是我要说的。

支持 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();

请告诉我这些做法如何和平共存。

术语的定义,

  • 信息专家:具有操作所需数据的对象应承载操作。

  • Tell Don't Ask:不要为了工作而向对象索取数据;告诉对象做这项工作。

  • 单一职责原则:每个对象都应该有一个狭义的职责。

4

6 回答 6

9

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

我们每天都必须就如何构建代码以及我们愿意在设计中引入哪些依赖项做出决定。

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

这些中的每一个都有助于检测我们设计中可能存在的不同类型的问题。对于您可能正在查看的任何特定问题,在某处都会有一个最佳位置。

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

对于您今天正在查看的具体问题,您需要确定可能导致您疼痛的最重要因素是什么。

于 2008-10-04T01:21:50.457 回答
5

当您询问对象的状态以告诉对象做某事时,您可以谈论“Tell Don't Ask”。

在您的第一个示例中,TransferObjectFactory.createFrom 只是一个转换器。它不会告诉客户对象在检查它的状态后做某事。

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

于 2010-06-02T06:25:36.393 回答
2

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

另一方面,.toDTO 方法是有问题的——为什么客户要承担这个责任?为了“纯粹”的缘故,我会有另一个班级,他的工作是从像客户这样的业务对象创建 DTO。

不要忘记这些原则就是原则,当您可以放弃更简单的解决方案直到不断变化的需求迫使问题出现时,然后这样做。不必要的复杂性绝对是要避免的。

我强烈推荐,顺便说一句,Robert C. Martin 的敏捷模式、实践和原则,以更深入地处理这个主题。

于 2008-10-04T09:12:23.143 回答
1

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

您将此 CustomerDTO 用于什么目的,为什么不能简单地使用 Customer,并在客户内部拥有 DTO 数据?如果您不小心,CustomerDTO 将需要 Customer,而 Customer 将需要 CustomerDTO。

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

一个例子是,如果您想提醒客户支付任何未结账单,那么您致电

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

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

Customer.RemindAboutOutstandingBills() 

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

信息专家说的差不多。

单一职责原则很容易被误解——它说客户类应该有一个职责,但也应该将与“客户”概念一致的数据、方法和其他类分组的职责只封装在一个类中。什么是单一职责很难准确定义,我建议您多阅读有关此事的内容。

于 2012-11-16T17:36:55.757 回答
1

Craig Larman 在 Applying UML and Patterns to Object-Oriented Analysis and Design and Iterative Development (2004) 中介绍 GRASP 时讨论了这一点:

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

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

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

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

因此,SRP 通常胜过 Information Expert。

但是,依赖倒置原则可以与 Expert 很好地结合。这里的论点是 Customer 不应该具有 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);
    }
}
于 2017-04-28T21:28:28.303 回答
0

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

如果您进一步分离问题并创建一个(或多个)专用对象来承担您拥有的个人职责,然后让控制对象将它正在使用的其他对象的实例传递给您已分割的专用对象,您应该能够观察到 SRP(每个职责都由一个专门的对象处理)和 Tell Don't Ask(控制对象告诉它正在组合在一起的专门对象做他们所做的任何事情)之间的愉快折衷,彼此)。

这是一种组合解决方案,它依赖于某种控制器在其他对象之间进行协调和委托,而不会陷入它们的内部细节。

于 2012-08-23T19:59:27.023 回答