【问题标题】:Confusion about single responsibility in OOP对 OOP 中单一职责的困惑
【发布时间】:2020-02-25 11:35:41
【问题描述】:

让我们考虑以下示例:

class User
{

}

class FirstUseNotification
{
    function show(User user)
    {
        // check if it was already shown, return if so
        // show a notification
        // mark it as shown in the db or whatever
    }
}

class SomeController
{
    function someMethod()
    {
        firstUseNotification->show(user);
    }
}

show() 方法似乎通过做 3 件事来打破单一职责。所以我认为这可以重写为:

class User
{

}

class FirstUseNotification
{
    function show(User user)
    {
        // show a notification
    }

    function shouldShow(User user)
    {
        // return true if not yet shown
    }

    function markAsShown(User user)
    {
        // flag notification as shown
    }
}

class SomeController
{
    function someMethod()
    {
        if (firstUseNotification->shouldShow(user)) 
        {
            firstUseNotification->show(user);
            firstUseNotification->markAsShown(user);
        }
    }
}

这就是我感兴趣的:

  1. 我是否正确假设在第二个示例中,通知类现在可以使用单一职责原则?
  2. show() 方法中发生的所有事情都消失了,但是......它们只是重新定位到控制器中的方法,所以这不应该意味着这个控制器方法现在破坏了单一职责吗?如果是这样,如何才能遵守?

【问题讨论】:

    标签: oop solid-principles single-responsibility-principle


    【解决方案1】:

    单一责任原则 (SRP) 经常以 Robert C. Martin 的名言形式表述:

    一个类应该只有一个改变的理由。

    在这种情况下,FirstUseNotification 类的目的是向首次使用的用户显示通知。所以这个类需要改变的唯一原因是这个目的是否改变了;这是一个原因,所以 SRP 是满意的。

    请注意,此定义适用于类,而不适用于方法。也就是说,将这个方法分成三个方法可能违反 SRP,因为如果这个类的用户需要调用三个方法而不是一个,那么该用户类有责任检查是否显示通知,并如图所示标记用户,除了用户类自己的责任。 FirstUseNotification 的职责是“向第一次使用的用户显示通知”,而不是提供允许其他类在不负责时执行此操作的 API。

    实际上,FirstUserNotification 类可能有其他原因要更改,如果它如何显示通知或访问数据库的详细信息发生更改。理想情况下,可以通过为通知和数据库类提供稳定的接口来防止这种情况发生,这样对这些类的更改不需要更改FirstUseNotification 或其他显示通知和/或访问数据库的类。在实践中,这并不总是 100% 实现,在这种情况下,FirstUseNotification 类可能有一些责任与显示通知或访问数据库的细节有关。但理论上,如果其他类处理好自己的职责,那么这个类只有一个改变的理由。

    【讨论】:

    • 感谢您的回答。但是,作为一个不相关的问题,在第一个示例中,是否违反了 SRP 之外的其他原则?我无法想象有一种具有副作用的方法(在这种情况下写入数据库)是一件好事。
    • 这里的原则是“避免副作用”。这是函数式编程的原则,但在 OOP 等其他范式中也有好处(如今,包括 Java 在内的大多数流行语言都是多范式)。好处包括使代码更容易推理,并且更容易与其他代码隔离测试。要按照当前设计的方式对FirstUserNotification 进行单元测试,您可能必须模拟通知显示和数据库更新部分;另一方面,您可以重新设计它以返回一个对象表示状态更改为...
    • ... 制作,这可能更容易测试,因为您可以测试它是否返回表示正确通知和状态更改的对象,而不是模拟协作者。另一方面,这可能会使其更难使用,因为您必须返回一个复杂的对象,该对象表示需要执行的多个副作用,并且您必须拥有另一个负责执行它们的类。通常对于 OOP,我建议在方便的地方避免副作用,但您并不总是需要。
    • 您可能还对与此相关的functional core, imperative shell 设计模式感兴趣。
    猜你喜欢
    • 2023-02-22
    • 2011-11-16
    • 1970-01-01
    • 2018-03-14
    • 2011-06-18
    • 1970-01-01
    • 1970-01-01
    • 2010-11-15
    • 2013-03-16
    相关资源
    最近更新 更多