Builders and Manipulators

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

这里是面向对象编程中命名方法的一个简单原则,我试图在我的代码中遵循它:如果方法是“操作”的话,它就是一个“动词”,如果方法是“构建”的话,它就是一个“名词”。就是这样,没有中间地带。像 saveFile()getTitle() 这样的方法不符合要求,必须重新命名和重构。此外,“操作”类的方法必须始终返回 void,例如 print()save()。让我解释一下。

首先,我必须说这个想法与 Bertrand Meyer 在他的书 面向对象软件构造 中提出的想法非常相似,他建议将对象的方法分为两个明确分离的类别:查询和命令。

这个原则背后的思想是相当哲学性的。让我们从构建者开始,构建者被认为是创建或查找一个对象,然后返回它。假设我有一个书店,我要求它按照书名给我一本书:

显然这是一个“构建器”(或者按梅耶的术语叫做“查询”)。我要求一本书,然后它就给了我。然而,问题在于方法的名称。它叫做“find”,这意味着我知道这本书将如何被处理。它将被找到。

然而,我们不应该这样对待我们的对象。我们不应该告诉它们如何完成我们想要它们完成的工作。相反,我们必须让它们决定这本书是被找到、构建还是从内存缓存中获取。当我们查询时,我们必须说明我们要找的结果,并让对象决定如何构建这个结果。这个方法的一个更合适的名称应该是book()

经验法则是:builder(建造者)总是一个名词。如果方法返回了某个东西,那它必须是一个名词。最好的情况是,方法的名称能解释方法返回的内容。如果它返回一本书,命名为 book()。如果它返回一个文件,将方法命名为 file(),等等。以下是一些良好的builder示例:

在这里,相反,以下是一些命名不当的构建者的例子:

建造者的名字中没有动词的位置!

顺便说一下,这不仅仅是关于名字的问题。由于建造者的名字不包含动词,它不应对封装的实体进行任何修改。它只能创建或找到某个东西并返回它。就像一个纯函数一样,它不能有任何副作用。

接下来,有一些”操作者”(或者在Meyer的术语中称为”命令”)。它们为我们做一些工作,修改由对象封装的实体。它们与建造者相反,因为它们实际上修改了对象所抽象出的世界。例如,我们要求Bookshelf在自身中添加一本新书:

该方法将书籍添加到存储中。关于存储将如何被修改,我们不知道。但我们知道,由于方法的名称是一个动词,将会有修改。

此外,操作器不能返回任何内容。我们总是将void作为其响应类型。这主要是为了将代码的命令部分与声明部分分开。我们要么接收对象,要么告诉它们该做什么。我们不能混合这些活动在一个方法中。

这些规则的目的是使代码更简单。如果你遵循这些规则,所有的构建器都只返回对象,而所有的操作器都只修改世界,整个设计将变得更容易理解。方法会更小,名称会更短。

当然,你经常会很难找到这些名称。有时候你会想要从操作器中返回一些东西,或者让你的构建器对缓存进行一些更改。尽量抵制这种诱惑,坚持原则:一个方法要么是构建器,要么是操作器,没有中间状态。上面的例子相当简单,实际生活中的代码要复杂得多。但这就是这个原则将帮助我们的地方——让代码变得更简单。

我也了解到了名词/动词原则,它建议将类的名称总是作为名词,将方法的名称作为动词。我认为这是一个错误的想法,因为它无法区分构建器和操作器,并鼓励我们始终以命令式指令的方式思考。我认为面向对象编程应该更多地关注对象的声明性组合,即使我们有时需要从其他对象中获取它们,而不是通过构造函数实例化它们。这就是为什么在大多数情况下我们需要构建器,并且我们也必须看到它们与其他方法(操作器)之间的明显区别。

你可以在Elegant Objects的第一卷第2.4节中找到关于这个问题的更详细讨论。

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-05 at 22:14

sixnines availability badge   GitHub stars