【问题标题】:SOLID - are the Single Responsibility Principle and the Open/Closed Principle mutually exclusive?SOLID - 单一职责原则和开放/封闭原则是否相互排斥?
【发布时间】:2018-03-09 15:46:18
【问题描述】:

单一职责原则指出:

一个班级应该有一个,而且只有一个改变的理由。

开放/封闭原则指出:

您应该能够扩展类行为,而无需修改它。

如果一个类应该只有一个改变的理由,但不应该被修改,那么开发人员如何同时遵守这两个原则?

示例

工厂模式是一个很好的例子,它具有单一职责,但可能违反开放/封闭原则:

public abstract class Product
{
}

public class FooProduct : Product
{
}

public class BarProduct : Product
{
}

public class ProductFactory
{
    public Product GetProduct(string type)
    {
        switch(type)
        {
            case "foo":
                return new FooProduct();
            case "bar":
                return new BarProduct();
            default:
                throw new ArgumentException(...);
        }
    }
}

当我需要在后期将ZenProduct 添加到工厂时会发生什么?

  • 这肯定违反了开闭原则?
  • 我们如何防止这种违规行为?

【问题讨论】:

  • @Ravi butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod 来自在开发者社区中享有盛誉的 Bob 叔叔。
  • 你能概括出一个矛盾吗?举个例子可能会很方便。
  • @0b101010 查看更新。
  • 在您的示例中,我不认为添加 ZenProduct 会违反任何一个原则,因为您只是在执行日常维护/改进 - 这两个原则都没有表明一旦编写了类就可以永远不会改变,事实上这种改变是首先使用工厂的关键原因之一。但是,如果您认为是这种情况,那么您应该使 GetProduct() 成为一个虚拟方法,从而仍然满足这两个原则
  • @LordWilmore OCP 声明“一个类应该被关闭以进行修改”。因此修改它添加ZenProduct违反了原则。我同意扩展工厂可以防止这种违规行为,但是开销很大,而且您还必须在其他地方违反 OCP,因为您必须修改一个类才能使用新工厂。

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


【解决方案1】:

这感觉像是在讨论“扩展类行为”的语义。将新类型添加到工厂是修改现有行为,而不是扩展行为,因为我们没有更改工厂所做的一件事。我们可能需要扩展工厂,但我们没有扩展它的行为。扩展行为意味着引入新的行为,并且在每次创建类型的实例或授权工厂的调用者时更符合事件的路线 - 这两个示例都扩展(引入新的)行为。

一个班级应该有一个,而且只有一个改变的理由。

问题中的示例是用于创建 Product 实例的工厂,它更改的唯一有效原因是更改它创建的 Product 实例的某些内容,例如添加新的 ZenProduct

您应该能够扩展类行为,而无需修改它。

实现这一点的一个非常简单的方法是使用Decorator

装饰器模式通常有助于遵守单一职责原则,因为它允许在具有独特关注领域的类之间划分功能。

public interface IProductFactory
{
    Product GetProduct(string type);
}

public class ProductFactory : IProductFactory
{
    public Product GetProduct(string type)
    {
        \\ find and return the type
    }
}

public class ProductFactoryAuth : IProductFactory
{
    IProductFactory decorated;
    public ProductFactoryAuth(IProductFactory decorated)
    {
        this.decorated = decorated;
    }

    public Product GetProduct(string type)
    {
        \\ authenticate the caller
        return this.decorated.GetProduct(type);
    }
}

装饰器模式在应用 SOLID 原则时是一种强大的模式。在上面的示例中,我们在ProductFactory 中添加了身份验证,而没有更改ProductFactory

【讨论】:

  • 真是巧合。我现在正在观看有关装饰器模式的复数教程!
【解决方案2】:

一个班级应该有一个,而且只有一个改变的理由。

这基本上意味着,您的类应该代表单一职责,之后不应修改以适应新功能。

例如,如果您有班级,负责以 pdf 格式打印报告。后来,您想添加新功能以支持以其他格式打印报告。然后不要修改现有代码,而是应该扩展它以支持其他格式,这也意味着扩展类行为,而不修改它

【讨论】:

  • 我同意这一点,但仍然有很多原则相互矛盾的例子。
  • @series0ne 可能,但我现在想不起任何例子,它们会相互矛盾。 :-)
  • 请看更新。我添加了一个示例。
【解决方案3】:

我认为这取决于您对 SRP 的解释。这东西总是有点主观。让 100 个人定义“单一职责”,你可能会得到 100 个不同的答案。

使用Ravi's answer 中的场景,一个典型的解决方案可能是拥有一个公开GeneratePdf 方法的ReportGenerator 类。如果需要,以后可以使用额外的GenerateWord 方法对其进行扩展。不过,和你一样,我认为这有点儿意思。

我可能会将GeneratePdf 方法重构为PdfReportGenerator 类,然后通过ReportGenerator 公开它。这样ReportGenerator 只有一个职责;这是暴露各种报告生成机制(但不包含它们的逻辑)。然后可以在不扩大该责任的情况下对其进行扩展。

我想说,如果您发现冲突,很可能是一种架构气味,需要快速审查,看看是否可以以更好的方式完成。

【讨论】:

    【解决方案4】:

    我有一个 StudentOrganiser 类,它需要 IStudentRepository 依赖项。 IStudentRepository 暴露的接口就是GetStudent(int studentId)

    类遵循 SRP,因为它没有任何与管理与存储库源的连接相关的逻辑。

    类遵循 OCP,因为如果我们想将存储库源从 SQL 更改为 XML,StudentOrganiser 不需要进行任何更改 => 对扩展开放但对修改关闭。

    考虑如果StudentOrganiser 被设计为不依赖IStudentRepository,那么类本身的方法必须负责实例化new StudentSqlRepository() 如果以后需要在基础上支持StudentXMLRepository在某些运行时条件下,您的方法将以某种 case switch 范式结束,因此违反了 SRP,因为方法也沉迷于实际的存储库决定因素。通过注入存储库依赖项,我们从类中移除了该责任。现在StudentOrganiser类可以扩展为支持StudentXMLRepository,无需任何修改。

    【讨论】:

    • 我想你在这里可能有点困惑。您没有通过注入不同的 IStudentRepository 实现来扩展 StudentOrganiser
    • @0b101010 :我很清楚 :) 更新了我的答案来解释这一点
    猜你喜欢
    • 1970-01-01
    • 2018-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-09
    相关资源
    最近更新 更多