代码之家  ›  专栏  ›  技术社区  ›  moffdub

难道信息专家/告诉别人不要问与单一责任原则不一致吗?

  •  24
  • moffdub  · 技术社区  · 16 年前

    信息专家 告诉别人不要问 SRP 经常被作为最佳实践一起提及。但我认为他们有分歧。这就是我要说的。

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

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

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

    • 告诉不要问:不要为了工作而向对象询问数据;告诉对象做这项工作。

    6 回复  |  直到 6 年前
        1
  •  8
  •   Hamish Smith    16 年前

    我不认为他们有太多的分歧,因为他们强调的是会给你带来痛苦的不同事情。一个是关于构造代码以明确特定职责的位置并减少耦合,另一个是关于减少修改类的原因。

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

    每一项都有助于检测我们的设计中可能存在的不同类型的问题。对于你可能看到的任何具体问题,都会有一个最佳点。

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

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

        2
  •  5
  •   Seva Parfenov    15 年前

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

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

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

        3
  •  2
  •   Phil Bennett    16 年前

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

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

    不要忘记这些原则是原则,当您可以使用更简单的解决方案,直到需求的变化迫使您解决问题时,请这样做。不必要的复杂性肯定是需要避免的。

        4
  •  1
  •   Michael Parker    12 年前

    带有姐妹类的DTO(就像您所拥有的)违反了您声明的所有三个原则,以及封装,这就是您在这里遇到问题的原因。

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

    举个例子,如果你想提醒客户支付任何未付的账单,那么你打电话给他

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

    这是违法行为。相反,你想做什么

    Customer.RemindAboutOutstandingBills() 
    

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

    信息专家也说了同样的话。

    单一责任原则很容易被误解——它说customer类应该有一个责任,但是分组数据、方法和其他与“customer”概念一致的类的责任应该只由一个类封装。构成单一责任的内容极难准确界定,我建议对此进行更多解读。

        5
  •  1
  •   Community CDub    5 年前

    Craig Larman在介绍如何将UML和模式应用于面向对象的分析和设计以及迭代开发(2004)时讨论了这一点:

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

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

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

    然而,依赖倒置原理可以很好地与专家相结合。这里的论点是,客户不应该依赖CustomerDTO(从常规到详细),而应该依赖CustomerDTO。这意味着CustomerDTO是专家,应该知道如何为客户构建自己:

    CustomerDTO dto = new CustomerDTO(bob);
    

    如果您对新产品过敏,可能会出现静电:

    CustomerDTO dto = CustomerDTO.buildFor(bob);
    

    或者,如果你两者都讨厌,我们回到一家抽象工厂:

    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
  •  0
  •   Ed Hastings    12 年前

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