单一责任原则-我是否过度使用它?


13

供参考-http://en.wikipedia.org/wiki/Single_responsibility_principle

我有一个测试场景,其中一个应用程序模块负责创建分类帐条目。可以执行三个基本任务:

  • 以表格格式查看现有分类帐条目。
  • 使用创建按钮创建新的分类帐条目。
  • 单击表中的分类帐条目(第一个指针中提到),然后在下一页查看其详细信息。您可以在此页面中使分类帐条目无效。

(每页中有几个其他的操作/验证,但为了简洁起见,我将其限制为这些操作/验证)

因此,我决定创建三个不同的类-

  • LedgerLandingPage
  • CreateNewLedgerEntryPage
  • ViewLedgerEntryPage

这些类提供了可以在那些页面中执行的服务,Selenium测试使用这些类将应用程序带入可以确定某些状态的状态。

当我和一位同事一起对它进行复习时,他感到不安,要求我为所有人开一门课。尽管我仍然觉得自己的设计很干净,但是我是否过度使用单一职责原则还是值得怀疑的


2
恕我直言,毫无意义地试图回答您的问题是毫无意义的,而无需了解两件事:这些类有多大(按方法计数和LOC)?在可预见的将来,它们有望增长多少/变得更复杂?
彼得Török

2
恕我直言,每种方法有10种,这清楚地表明不要将代码与30种方法归为一类。
布朗

6
这是边界区域:100个LOC和10个方法的类不太小,而300个LOC的类也不太大。但是,单一类中的30种方法对我来说听起来太多了。总的来说,我倾向于同意@Doc的观点,因为拥有许多小班课程比拥有一个超重课程的风险要小。尤其是考虑到班级自然地倾向于体重增加随着时间的推移...
彼得Török

1
@PéterTörök-我同意,除非可以将代码重构为一个可以直观使用的类,该类可以重用代码,而不是像我期望的OP那样复制功能。
SoylentGray 2011年

1
也许应用MVC可以清除所有问题:三个视图,一个模型(Ledger)和三个控制器。
凯文·克莱恩

Answers:


19

在这里引用@YannisRizos :

这是一种本能的方法,可能是正确的,因为更可能改变的是管理分类帐的业务逻辑。如果发生这种情况,维护一个班级要比维护三个班级容易得多。

经过大约30年的编程经验,至少在现在,我不同意。在我能想到的几乎每种情况下,较小的类IMHO都更好地维护。放在一个类中的功能越多,结构就越少,代码的质量也会下降-每天都有一点。当我正确理解Tarun时,这3个操作中的每个操作

LedgerLandingPage

CreateNewLedgerEntryPage

ViewLedgerEntryPage

在一个用例中,每个类都包含一个以上的函数来执行相关任务,并且每个类都可以单独开发和测试。因此,为每个类创建类将归为一体的事物组合在一起,从而可以轻松地确定要更改的代码部分。

如果您在这3个类之间具有共同的功能,则它们要么属于一个共同的基类,要么属于Ledger该类本身(我假设您至少有一个针对CRUD操作),或者属于一个单独的帮助器类。

如果您需要更多的参数来创建许多较小的类,建议阅读Robert Martin的书“ Clean Code”


这就是我提出了三个差异类而不是将所有内容都推入一个类的全部原因。由于这些功能之间没有通用的功能,因此我没有任何通用的类。感谢链接。
塔伦(Tarun)

2
“几乎在我能想到的所有情况下,都最好维护较小的类。” 我认为,即使Clean Code也不会走得那么远。您是否真的会争辩说,例如,在MVC模式中,每页应该有一个控制器(或者说用例)?在重构中,Fowler在一起具有两个代码嗅觉:一个是指有多种理由来更改类(SRP),另一个是指必须为一个变更而编辑许多类。
pdr

@ pdr,OP指出这些类之间没有通用的功能,因此(对我而言)很难想象需要触摸多个来进行单个更改的情况。
彼得Török

@pdr:如果您要更改的类太多,则主要是您没有将通用功能重构到另一个地方的信号。但是在上面的示例中,这可能会导致第四类,而不仅仅是第四类。
布朗

2
@pdr:顺便说一句,您实际上读过“清洁代码”吗?鲍勃·马丁(Bob Martin)在将代码分解成较小的单元方面走得很远。
布朗

11

没有单一责任原则或其他任何原则的明确实现。您应该根据更可能更改的内容来决定。

正如@Karpie在较早的答案中写道:

您只需要一个类别,该类别的唯一职责是管理分类帐。

这是一种本能的方法,可能是正确的,因为更可能改变的是管理分类帐的业务逻辑。如果发生这种情况,维护一个班级要比维护三个班级容易得多。

但这应根据情况进行检查和决定。您不应该真正关心微小的变更可能性,而将原则应用在更可能发生变化的地方。如果管理分类帐的逻辑是一个多层过程,并且各个层易于独立更改,或者在原则上应分开关注(逻辑和表示),则应使用单独的类。这是一场概率游戏。

关于过度使用设计原则的类似问题,我想您会发现很有趣:设计模式:何时使用以及何时停止使用模式做所有事情


1
据我了解,SRP并不是真正的设计模式。
布朗

@DocBrown当然不是设计模式,但是关于这个问题的讨论是非常相关的...虽然很好,但是我已经更新了答案
yannis 2011年

4

让我们检查一下这些类:

  • LedgerLandingPage:此类的作用是显示分类帐列表。随着应用程序的增长,您可能希望添加用于排序,过滤,高亮显示和透明融合其他数据源数据的方法。

  • ViewLedgerEntryPage:此页面详细显示分类帐。似乎很简单。只要数据简单明了。您可以在此处使分类帐无效。您可能还需要更正它。或对其进行评论,或附加文件,收据或外部预订号或其他内容。一旦允许编辑,您肯定要显示历史记录。

  • CreateLedgerEntryPage:如果ViewLedgerEntryPage具有完全编辑功能,则可以通过编辑“空白条目”来实现此类的责任,这可能有意义,也可能没有意义。

这些示例似乎有些牵强,但关键是,我们在UX中讨论的是功能。单一功能绝对是一种独特的单一责任。因为对该功能的要求可以更改,并且两个不同功能的要求可以更改为两个相反的方向,所以您希望从一开始就坚持使用SRP。
但是相反地,如果您出于任何考虑而拥有一个单一的界面更方便,则始终可以将外观划分为三个类。


SRP过度使用是一种情况:如果您需要十几个类来读取文件的内容,则可以很安全地假设您正在这样做。

如果从另一端看SRP,它实际上说的是,一个班级应该承担单个责任。但是请注意,责任仅存在于抽象级别内。因此,从较高抽象级别的类通过将工作委派给较低级别​​的组件来履行其职责是完全有意义的,这最好通过依赖反转来实现。


我想我什至无法比您更好地描述这些课程的责任。
塔伦(Tarun)

2

这些课程只有一个改变的理由吗?

如果您还不知道(那么),那么您可能已经进入了投机设计/过度工程陷阱(太多的类,应用了太复杂的设计模式)。您不满意YAGNI。在软件生命周期的早期阶段(某些)要求可能不清楚。因此,您当前看不到变化的方向。如果您意识到这一点,请将其放在一个或最少的类中。但这带来了一个责任:一旦明确了需求和变更方向,就需要重新考虑当前的设计并进行重构以满足变更的原因。

如果您已经知道存在三种不同的更改原因,并且它们映射到这三个类别,那么您只需应用正确剂量的SRP。同样,这也带来一些责任:如果您错了,或者将来的需求发生了意外更改,则需要重新考虑当前的设计并进行重构以满足更改的原因。

重点是:

  • 随时注意驱动程序以进行更改。
  • 选择一个灵活的设计,可以对其进行重构。
  • 准备重构代码。

如果您有这种想法,那么您就可以对代码进行整形,而不必担心投机设计/过度设计。

但是,总会有时间因素:对于许多更改,您将没有所需的时间。重构需要花费时间和精力。我经常遇到我必须更改设计的情况,但是由于时间限制,我不得不推迟设计。这将发生并且无法避免。我发现在工程代码下重构比在工程代码上重构更容易。YAGNI给了我一个很好的指导:如有疑问,请将类的数量和设计模式的使用降至最低。


1

调用SRP作为拆分类的原因有三个:

  • 这样将来的需求变化可以使某些类别保持不变
  • 以便可以更快地隔离错误
  • 使班级变小

拒绝分组的原因有三个:

  • 这样将来的需求变更不会导致您在多个类别中进行相同的变更
  • 以便发现错误不会涉及跟踪间接的长路径
  • 抵制破坏封装的技术(属性,朋友等),这些技术仅在一个相关类需要时才向所有人公开信息或方法

当我查看您的情况并想象更改时,其中一些会导致对多个类的更改(例如,将字段添加到分类帐中,您需要更改所有三个类别),但是许多情况不会更改-例如,将分类添加到分类帐列表中或在添加新分类帐条目时更改验证规则。您同事的反应可能是因为很难知道在哪里寻找错误。如果分类账清单上出现问题,是因为错误地添加了分类帐,还是列表有问题?如果摘要和详细信息之间存在差异,那是错误的?

您的同事也可能认为,要求变更所有三个班级的要求变更的可能性远比您只更改一个班级的变更更容易。那可能是一个非常有用的对话。


很高兴看到不同的观点
Tarun

0

我认为如果分类账是db中的表,我建议将这些任务放在一个类(例如DAO)中。如果分类账有更多逻辑并且不是db中的表,我建议我们应该创建更多的类来执行这些任务(可能是两个或威胁类),并提供仅用于客户的外观类。


好吧,分类帐是UI中的一项功能,与此相关的许多操作都可以在不同的页面上执行。
塔伦(Tarun)

0

恕我直言,您根本不应该使用类。这里没有数据抽象。分类帐是一种具体的数据类型。操纵和显示它们的方法是功能和过程。当然,将共享许多常用功能,例如格式化日期。在显示和选择例程中,几乎没有任何数据可以抽象并隐藏在类中。

在某些情况下,可以在类中隐藏状态以进行分类帐编辑。

无论您是否滥用SRP,您都在滥用更基本的东西:您过度使用了抽象。这是一个典型的问题,由OO神话概念引起,是一个理智的开发范例。

编程是90%具体的:数据抽象的使用应该很少并且经过精心设计。另一方面,功能抽象应该更常见,因为语言细节和算法选择需要与操作的语义分开。


-1

您只需要一个类别,该类别的唯一职责是管理分类帐


2
对我而言,“ ...经理”类通常表明您不费心将其进一步分解。什么是班级管理?介绍,坚持不懈?
sebastiangeiger

-1。SRP试图避免将3个或更多用例放在一个类中,这是有充分理由的。
Doc Brown

@sebastiangeiger那么,OP的三个类在做什么?表现还是坚持?
sevenseacat 2011年

@Karpie我真的很希望演讲。当然,该示例并不是很好,但是我希望网站中的“ ...页面”在执行类似于视图的操作。
sebastiangeiger

2
事实上,你只需要一个类为整个应用程序,用的单一职责使用户满意 ;-)
彼得Török
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.