【问题标题】:Are SOLID principles really solid?SOLID 原则真的很可靠吗?
【发布时间】:2010-06-08 14:01:09
【问题描述】:

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

单一责任原则 声明每个对象都应该有一个 单一的责任,那 责任应该完全 由类封装。

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

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

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

【问题讨论】:

  • @C.罗斯:我认为这足够具体,不属于社区 Wiki 条目。
  • 如果你将一个类的职责定义为“做它被编码做的事情”,那么 SRP 就是自我实现的。所以这真的取决于你如何定义“责任”。 “实现 X 的特性,包括序列化、比较……”我认为这是一个单一的职责。单一职责 =/= 单一功能。

标签: c# .net design-patterns solid-principles


【解决方案1】:

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

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

您只需要学会找到正确的平衡点:不要让您的应用程序陷入混乱的深渊,但不要让自己被众多抽象层束缚住手脚。

【讨论】:

  • 同意在不理解规则的情况下试图遵守规则是一个大错误。规则是有原因的,当你的上下文不符合这些原因时,你必然有一个例外。
  • SOLID 可能需要在首字母缩略词中添加另一个字母:P 代表“选择你的战斗”,以强调设计是妥协的事实。这篇文章中有几个很好的轶事:martinfowler.com/ieeeSoftware/protectedVariation.pdf
【解决方案2】:

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

【讨论】:

  • 同意。 SRP 是 SOLID 原则中最“坚实”的,IMO。其他人可能会偶尔弯曲,但绝不应违反 SRP。
  • @Stephen:我认为这里的诀窍是了解什么是单一职责。在实践中,这会有所不同,具体取决于对班级的期望,以及“玩得好”的要求。
  • 取决于此属性的作用。它可能会增加很多责任。
  • 好吧,我不这么认为。接口或属性只是工具。它们扩展了类的行为。
  • @Developer Art:我会说添加接口或属性确实会更改类,但不会必然更改其责任。这完全取决于添加的内容。例如,IDisposable 是 GC 管道的问题,而不是功能。另一方面,如果一个用户类突然实现了 IEnumerable,那将违反 SRP(和常识)。
【解决方案3】:

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

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

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

不过,请坚持下去。虽然可能不可能在所有情况下都应用 - 它仍然比在代码中使用单个“上帝对象”(反模式)要好。

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

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

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

【讨论】:

  • Clean Code 是我读到的书,当我终于看到 OO/Testable-code 的光芒突然出现在我的头上时。这本书最好的部分是用之前/之后的示例清理代码的地方。真正让我印象深刻的是我们的代码在视觉上看起来与以前有多少相似之处,它确实帮助我理解为了理智而将代码移动到哪个方向
【解决方案4】:

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

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

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

【讨论】:

  • 或者,换句话说,您没有利用 OO。这是不好的,不是中立的。
  • @StevenSudit 或者,根据上下文,您正在避免 OO 的缺点
  • @KoIA 在做 OO 时避免 OO 缺点的方法是遵循 SOLID 等原则。淡化 OO,尤其是以无原则的方式,充满了缺点。
  • @StevenSudit 如果你不想做 OO 那么你不应该使用 OOP 语言。如果您使用的是 OOP 语言,您应该了解如何进行高质量的 OOP 工程——SOLID 汇总了很多内容。
  • @STW 我基本同意。这些只是很好的设计原则,专门应用于 OOP。即使您有正当理由以较少 OO 的方式使用 C++,您也应该坚持适用的设计原则。因此,例如,模板繁重的代码可以减少 OO,但这并不意味着它应该是不好的。
【解决方案5】:

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

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

【讨论】:

  • 这很有趣。我可以告诉其他方法。记得我读过一本关于 DDD(领域驱动设计)的书,作者建议不要写属性,而是将序列化责任转移到其他类。他们将其命名为存储库。
  • @Arseny:这很有趣,但我看不出它在 .NET 环境中如何运作良好。
【解决方案6】:

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

话虽如此,假设您的序列化/反序列化代码在某个其他类中,向属性添加属性并没有真正向类添加任何功能或行为。您只是在添加有关您的类结构的信息,因此这似乎没有违反原则。

【讨论】:

    【解决方案7】:

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

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

    【讨论】:

      【解决方案8】:

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

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

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

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

      【讨论】:

      • 我所看到的关于 SRP 的讨论的一个大问题是,它们无法区分类型的公众面孔及其实现。如果代码应该模拟一个具有许多能力的真实世界对象,它应该创建一个类的实例,该实例公开公共成员以访问这些能力。如果能力的数量很大,可能需要让公开这些能力的类主要用作私有对象的包装器,并将外部公共成员链接到封装对象的那些。
      • 如果MonitorableHeartInjectableBloodStream 是单独的类,如果给定一个与一只狗相关联的MonitorableHeart 和与另一只狗相关联的InjectableBloodStream,则类似while(heart.rate() > 0 && heart.rate() < 70) bloodstream.inject(drugs.someStimulant); 的代码将表现得非常糟糕。但是,如果 "request-heart-rate" 和 "inject drug" 方法都属于同一个 Dog 对象,那么这个问题就会消失。
      • 但是,如果将来您要设计更多可以“睡觉”、“吃饭”或“吠叫”的生物,您可能会有很多重复的代码。最好将 Dog 设计为他的功能的组合,因为如果您需要另一种类型的动物,您可以创建不同的组合。至于@supercat 的cmets,我不太明白。
      • 我从来没有说过你应该写两次代码。在这种情况下,我将创建一个“Creature”类,而“Dog”将从它派生。无论如何,这就是所有那些所谓的“原则”的问题。没有人对此有完全相同的想法。对于名为计算机科学的领域,恕我直言,这是一个大问题。另外,我不知道谁/为什么不赞成我的回答。
      • @RockyMM:假设一个人希望有一种方法,通过监测心率并在必要时注射药物将其保持在该范围内,从而试图将患者的心率保持在一定范围内。我认为让该方法接受对支持查询速率和注射药物的方法的对象的单个引用通常比让它接受对独立对象的引用要好得多。这样的设计可能需要偶尔构建人工代理对象,这些代理对象包含对心脏监测设备和注射泵的引用,但是......
      【解决方案9】:

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

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

      【讨论】:

      • “SOLID 原则非常流畅”我喜欢 ))
      • 这是 SOLID 和 OOP 的普遍问题:最佳实践描述得如此含糊,几乎没有用,当人们未能遵循它们时,总会有普遍的免责声明“这是因为你做错了” :)
      【解决方案10】:

      什么是责任?在the words of Robert C. Martin (aka. Uncle Bob) 本人:

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

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

      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 非常不稳定。它会因任何这些原因而改变。

      来源:Chapter 76. The Single Responsibility Principle (from the book: "97 Things Every Programmer Should Know")

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

      来源:Chapter 9 - Single Responsibility Principle

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

      中肯的批评:

      坚实的防御:

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

      【讨论】:

        【解决方案11】:

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

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

        参考:Clean Code 中的 Bob Martin 叔叔

        【讨论】:

          【解决方案12】:

          S.O.L.I.D 代表:

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

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

          您可以在http://www.slideshare.net/jonkruger/advanced-objectorientedsolid-principleshttp://www.slideshare.net/jonkruger/advanced-objectorientedsolid-principles 处查看关于此主题的很好解释的演示文稿

          【讨论】:

            猜你喜欢
            • 2017-01-04
            • 2013-01-01
            • 2021-03-27
            • 1970-01-01
            • 2010-12-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多