【问题标题】:Aren't Information Expert & Tell Don't Ask at odds with Single Responsibility Principle?Information Expert & Tell Don't Ask 不与单一职责原则相悖吗?
【发布时间】:2008-10-04 00:48:40
【问题描述】:

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); 

支持告诉-不询问和信息专家但违反 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:不要为了工作而向对象索取数据;告诉对象做这项工作。

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

【问题讨论】:

  • 对您正在使用的术语的简短介绍可能会有所帮助。
  • 简短的回答是肯定的......长期的一个是有时人们(学者)忘记编程还不是一门完美的科学,..我们没有足够干净的语言来满足所有这些原则。打破最有意义的原则。
  • 你的第二个例子确实违反了 SRP,但并不是一个告诉不要问的好例子。

标签: oop solid-principles single-responsibility-principle tell-dont-ask


【解决方案1】:

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

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

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

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

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

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

【讨论】:

  • 所以,all SOLID 原则至少在某些情况下不能同时得到满足——也就是说,可能会损害并且不是“越多越好”的指标 :)
  • 确实如此。这就是为什么编程是一门艺术,而不是盲目地应用规则。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

      【解决方案4】:

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

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

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

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

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

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

      Customer.RemindAboutOutstandingBills() 
      

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

      信息专家说的差不多。

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

      【讨论】:

        【解决方案5】:

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

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

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

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

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

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

        但是,依赖倒置原则可以很好地与专家相结合。这里的论点是 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);
            }
        }
        

        【讨论】:

          【解决方案6】:

          我不是 100% 同意您的两个示例具有代表性,但从一般的角度来看,您似乎是根据两个对象且只有两个对象的假设进行推理。

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-01-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-03-14
            • 1970-01-01
            相关资源
            最近更新 更多