有超过 1 种方法不会破坏单一职责原则吗?

Doesn't having more than 1 method break the Single Responsibility Principle?

我对单一职责原则很困惑。 原则指出 class 更改的原因应该只有一个。

我面临的问题是,对方法的任何更改或做事中的任何逻辑更改都会更改 class。例如,考虑以下 class:

class Person{
    public void eat(){ };
    public void walk(){ };
    public void breathe(){ };
    public void run(){ };
    public void driveCar(Car car){ };
}

Bob 大叔将其描述为 应该只有一个 person/Actor 负责更改 。我有以下两个问题:

  1. 以上class谁是actor/Person谁可以负责更改?
  2. 进食、呼吸或行走的任何逻辑改变都不会改变 class 人吗?那么这是否意味着每一种方法都是改变的理由,因为它做事的逻辑可能会改变?

有趣的问题。来自 "Uncle Bob" Martin 的 quote 是:

A class should have one, and only one, reason to change.

可以将此解释为您的 Person class 有五个更改原因:您可能想要更改 eat 方法,或更改 walk方法,或breathe方法,或run方法,或driveTheCar方法。但这太狭隘了,并没有真正理解 Martin 所说的 "reason to change".

的意思

更改 class 的原因意味着人类程序员更改它的动机。您不会仅仅因为有改变 eat 方法的动机而改变 eat 方法;您可以更改它以实现有关程序所需行为的某些目标。

如果 Person class 模拟一个人进行某种模拟,那么你改变它的动机就是你想要 "to change how people's actions are modelled in the simulation". 您对 class 所做的每一个 更改都会出于该原因,无论您更改了一种方法还是多种方法;所以 Person class 只有一个 "reason" 需要改变,满足 SRP。

如果Person class有一些其他的方法比如在屏幕上画人物,那么你可能还需要"to change the graphical appearance of your simulated people".这与改变你的模拟模拟人们行为的方式的动机完全不同,所以 class 有两个责任,违反了 SRP。


改变的原因是什么

  1. For the above class who is the actor/Person who can be responsible for the change?

一个Actor是一个用户(包括客户、利益相关者、开发人员、组织)或一个外部系统。 我们可以争论人是否是系统,但事实并非如此。

另请参阅:Use case

  1. Wouldn't any change in the logic of eating, breathing or walking change the class Person? So doesn't that mean that every method is a reason to change as its logic to doing things might change?

不,方法不是改变的理由。方法是可以改变的东西……但为什么会这样呢?什么会触发开发人员更改它?


单一职责原则的一部分是代码最多只能与一个外部系统交互。 请记住,并非所有参与者都是外部系统,但是,有些是。我认为大多数人会发现 SRP 的这一部分很容易理解,因为与外部系统的交互是我们可以在代码。

然而,这还不够。例如,如果您的代码必须计算税收,您可以在代码中硬编码税率。这样,它就不会与任何外部系统交互(它只是使用常量)。然而,在一项税制改革之后,政府被披露为更改代码的原因。


您应该能够做的是交换外部系统(可能需要一些额外的编码工作)。例如,从一个数据库引擎更改为另一个。但是,我们不希望这些更改中的任何一项转化为对代码的完全重写。更改不应传播,进行更改不应破坏其他内容。为确保这一点,我们希望隔离处理数据库引擎(在本例中)的所有代码。

Things that change for the same reasons should be grouped together, things that change for different reasons should be separated. -- Robert C Martin

我们可以对上面的政府示例做类似的事情。我们可能不希望软件读取会议纪要,相反,我们可以让它读取配置文件。现在外部系统是文件系统,会有与之交互的代码,而该代码不应该与其他任何东西交互。


我们如何确定这些改变的原因?

您的代码由一组要求定义。有些是功能性的,有些则不是。如果这些要求中的任何一个发生变化,您的代码也必须更改。更改需求的原因就是更改代码的原因。

注意:您可能没有记录所有需求,但是,未记录的需求仍然是一个需求。

然后,你需要知道这些需求是从哪里来的。谁或什么可以改变他们?这些就是你改变的原因。这可能是公司政治的变化,可能是我们正在添加的功能,可能是新法律,可能是我们正在迁移到不同的数据库引擎或不同的操作系统,翻译成另一种语言,适应另一个国家等

其中一些是您的代码与之交互的外部系统(例如数据库引擎),一些不是(公司的政策)。


责任怎么办

您想隔离他们。因此,您将拥有与数据库交互的代码,仅此而已。您将拥有实现业务规则的代码,仅此而已。等等。

意识到即使代码的每个部分的实现都依赖于外部的东西,它们的接口也不一定。因此,定义接口并注入依赖项,这样您就可以更改每个部分的实现而不必更改其他部分……也就是说,部分代码的实现不应成为更改代码其他部分实现的理由.

注意:代码的任何部分都不应具有多重职责。让你的部分代码处理每个职责,让你的部分代码负责将其他部分组合在一起。同样,如果您的代码的一部分没有责任……就没有理由保留它。因此,您的代码的每一部分都应该只负责一个。

对于你的代码,问问自己,Personclass的要求是什么。它们完整吗?他们从哪里来?他们为什么要改变?


关于单一职责原则更权威的解释,参见2015年挪威开发者大会Robert C Martin - The Single Responsibility Principle(51分8秒,英文)