43

这个首字母缩写词中的第一个字母代表的设计模式是单一责任原则。这是一个报价:

单一职责原则指出每个对象都应该有单一职责,并且该职责应该完全由类封装。

在我们开始编码之前,这很简单明了。假设我们有一个定义明确的单一职责的类。要序列化类实例,我们需要向该类添加特殊属性。所以现在这个班级有另一个责任。这不违反 SRP 吗?

让我们看另一个例子——一个接口实现。当我们实现一个接口时,我们只需添加其他职责,比如处理它的资源或比较它的实例等等。

所以我的问题。是否可以严格遵守 SRP?如何做呢?

4

12 回答 12

79

正如您有一天会发现的那样,软件开发中最广为人知的原则中没有任何一条可以 100% 遵循。

编程通常是关于做出妥协——抽象的纯粹性 vs. 代码大小 vs. 速度 vs. 效率。

您只需要学会找到正确的平衡点:不要让您的应用程序陷入混乱的深渊,但不要将自己与众多抽象层捆绑在一起。

于 2010-06-08T14:06:46.253 回答
15

我不认为可序列化或一次性使用意味着多重责任。

于 2010-06-08T14:04:36.823 回答
10

好吧,我想首先要注意的是,这些只是好的软件工程原则——你也必须做出判断。所以从这个意义上说 - 不,它们并不坚固(!)

我认为你提出的问题提出了关键点——你如何定义班级应该有的单一职责?

在定义职责时不要过于拘泥于细节,这一点很重要——仅仅因为一个类在代码中做了很多事情并不意味着它有很多职责。

但是,请坚持下去。尽管可能不可能在所有情况下都适用 - 它仍然比在您的代码中使用单个“上帝对象”(反模式)更好。

如果您遇到这些问题,我建议您阅读以下内容:

  • Refactoring - Martin Fowler:虽然它显然是关于重构的,但这本书在展示如何将问题分解成逻辑部分或责任方面也非常有帮助——这是 SRP 的关键。这本书还涉及其他原则——但是它的学术方式比你以前看到的要少得多。

  • 清洁代码 - 罗伯特马丁:谁比 SOLID 原则的最大代表更适合阅读。说真的,我发现这本书对软件工艺的所有领域都非常有用——不仅仅是 SOLID 原则。就像福勒的书一样,这本书适用于所有经验水平的人,所以我会推荐给任何人。

于 2010-06-08T14:08:03.997 回答
9

为了更好地理解 SOLID 原则,您必须了解它们解决的问题:

面向对象编程源于结构化/过程编程——它增加了一个新的组织系统(类等)以及行为(多态性、继承、组合)。这意味着 OO 不是与结构化/程序化分离的,而是一种进步,如果开发人员愿意,他们可以做非常程序化的 OO。

所以... SOLID 的出现就像一个试金石,用来回答“我是真的在做 OO,还是我只是在使用程序对象?”这个问题。如果遵循这 5 条原则,则意味着您离 OO 方面还很远。不符合这些规则并不意味着你没有做 OO,但它意味着它的结构/程序 OO 更多。

于 2010-06-08T14:29:50.170 回答
6

这里有一个合理的关注点,因为这些交叉关注点(序列化、日志记录、数据绑定通知等)最终会向多个类添加实现,这些类只是为了支持其他一些子系统。这个实现必须经过测试,所以这个类肯定承担了额外的责任。

面向方面的编程是尝试解决此问题的一种方法。C# 中的一个很好的例子是序列化,对于不同类型的序列化,有很多不同的属性。这里的想法是类不应该实现执行序列化的代码,而是声明它是如何被序列化的。元数据是一个非常自然的地方,可以包含对其他子系统很重要但与类的可测试实现无关的细节。

于 2010-06-08T21:37:39.297 回答
2

关于设计原则要记住的是总是有例外,你不会总是发现你的场景/实现与给定的原则 100% 匹配。

话虽如此,假设您的序列化/反序列化代码在其他某个类中,向属性添加属性并没有真正向类添加任何功能或行为。你只是在添加关于你的类结构的信息,所以它看起来并不违反原则。

于 2010-06-08T14:08:15.977 回答
2

我认为一个类可以完成许多次要的、常见的任务,而不会掩盖类的主要职责:序列化、日志记录、异常处理、事务处理等。

就您的业务/应用程序逻辑而言,您的类中的哪些任务构成实际责任,以及什么只是管道代码,这取决于您的判断。

于 2010-06-08T14:22:06.240 回答
2

那么,如果不是用“Bark”、“Sleep”和“Eat”方法设计一个“Dog”类,我必须设计“AnimalWhoBarks”、“AnimalWhoSleeps”、“AnimalWhoEat​​s”等类?为什么?这如何使我的代码变得更好?我应该如何简单地实现我的狗如果不吃东西就不会睡觉并且会整夜吠叫的事实?

“将大类拆分成小类”是一个很好的实用建议,但“每个对象都应该有一个单一的职责”是绝对的 OOP 教条废话。

想象一下 .NET 框架是在考虑 SRP 的情况下编写的。您将拥有数百万个课程,而不是 40000 个课程。

广义 SRP(“广义”是这里的重要词)只是恕我直言的犯罪。您不能将软件开发简化为 5 个书本原则。

于 2014-11-23T07:03:41.477 回答
1

通过更改您对“单一责任”的定义 - SOLID 原则非常灵活,并且(与其他吸引人的首字母缩略词一样)并不意味着它们看起来的意思。

它们可以用作清单或备忘单,但不能用作完整的指南,当然也不能用作学习材料。

于 2010-06-08T14:52:36.807 回答
1

什么是责任?用Robert C. Martin(又名 Bob 叔叔)自己的话来说:

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

SRP 中的“单一责任”和“改变的一个理由”一直是许多混乱的根源(例如Dan NorthBoris)。很可能是因为代码片段的职责当然可以由程序员任意构思和指定。此外,更改一段代码的原因/动机可能与人类一样多方面。但鲍勃大叔的意思是“商业理由”

SRP 是在多个团队必须协调工作的企业业务环境中构思的,重要的是他们可以在大多数时间独立工作,在各自的业务子单元(团队/部门等)内。所以他们不会过多地踩对方的脚趾(这也解释了 SOLID 对接口的迷恋..),因此更改代码以满足业务请求(业务用例或业务部门的典型请求)可以本地化和凝聚力,对代码进行有针对性和孤立的更改,而不涉及通用/全局代码。

总结:SRP 意味着代码应该有“一个业务原因”来改变,并且有“单一的业务责任”。

从原始资料中可以看出这一点,Robert C. Martin aka “Uncle Bob” 写道(括号和重点是我的):

把那些因为相同原因而改变的东西聚集在一起,把那些因为不同原因而改变的东西分开。

这一原则通常被称为单一责任原则,或 SRP。简而言之,它说一个子系统、模块、类,甚至一个函数,不应该有多个改变的理由

这一原则通常被称为单一责任原则,或 SRP。简而言之,它说一个子系统、模块、类,甚至一个函数,不应该有多个改变的理由。经典示例是一个具有处理业务规则、报告和数据库的方法的类:

public class Employee {
    public Money calculatePay() ...
    public String reportHours() ...
    public void save() ...
}

一些程序员可能认为将这三个函数放在同一个类中是非常合适的。毕竟,类应该是对公共变量进行操作的函数的集合。然而,问题在于这三个功能的改变是出于完全不同的原因。只要计算工资的业务规则发生变化,calculatePay 函数就会发生变化。每当有人想要报告的不同格式时,reportHours 函数都会发生变化。只要 DBA [数据库管理员] 更改数据库模式,保存功能就会更改。这三个改变的原因结合起来使得Employee非常不稳定。它会因任何这些原因而改变。

资料来源:第 76 章。单一职责原则(来自书:“每个程序员应该知道的 97 件事”)

为什么将这两个职责分成不同的类很重要?因为 每一项责任都是一个变化的轴心。当 需求发生变化时,这种变化将通过类之间的责任变化来体现。如果一个类承担了不止一种责任,那么它改变的理由就会不止一个。如果一个类有多个职责,那么职责就会耦合。改变一项职责可能会削弱或抑制班级满足其他职责的能力。这种耦合会导致脆弱的设计,在更改时会以意想不到的方式破坏。

资料来源:第 9 章 - 单一职责原则

如果 Bob 叔叔不写“原因”而写“商业原因”,就可以避免很多误解,只有在以后阅读和解释细则时才会更清楚。或者,如果人们去SOLID 的原始来源并彻底阅读它,而不是通过网上的传闻版本。

坚实的批评:

坚实的防御:

PS:SRP 可以与其密切相关的兄弟BoundedContext进行比较,这可能最好被Sam Newman 在 12:38定义为“由明确边界强制执行的特定责任” 。许多这些原则只是对首要的重要软件原则Prefer Cohesion over Coupling的推导/重述。Bob 大叔甚至在介绍SRP时说:“这个原则在 Tom DeMarco 和 Meil​​ir Page-Jones 的工作中有所描述。他们称之为内聚力。正如我们将在第 21 章中看到的,我们对包内聚力有一个更具体的定义级别。但是,在类级别的定义是相似的。

于 2021-10-21T08:56:32.427 回答
0

在我看来,SRP 是一个有点模糊的术语。没有人能清楚地定义一种责任应该是什么。我实现它的方式是我严格将我的方法大小保持在 40 以下,目标在 15 以下。

尽量遵循常识,不要过分沉迷于它。尝试将类保持在 500 行以下,方法最多保持在 30 行以下。这将允许它适合单个页面。一旦这成为您的习惯,您就会注意到扩展代码库是多么容易。

参考:清洁代码中的 Bob Martin 叔叔

于 2019-01-07T03:48:28.243 回答
-2

固体代表:

  • 单一责任原则
  • 开闭原则
  • Liskov 的替代原则
  • 接口隔离原理
  • 依赖倒置原则

当我们谈论 OOP 时,这些是我们所指的标准。然而,这些原则中没有一个可以在软件开发中完美地实现。

您可以在此处查看有关此主题的非常好的解释演示http://www.slideshare.net/jonkruger/advanced-objectorientatedsolid-principles

于 2014-05-22T10:43:55.193 回答