Object Behavior Must Not Be Configurable

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

使用对象属性作为配置参数是我们经常犯的一个非常常见的错误,主要是因为我们的对象是可变的—我们对它们进行“配置”。我们通过将参数甚至整个设置/配置对象注入到它们中来改变它们的行为。我需要说这在哲学角度上是滥用和不尊重的吗?我可以这么说,但让我们从实际角度来看一下。

假设有一个类,它应该读取一个网页并返回其内容:

看起来简单而直观,对吧?是的,这是一个相当有凝聚力和稳定性的类。以下是我们如何使用它来读取Google首页的内容:

一切都很好,直到我们开始让这个类更强大。假设我们想要配置编码。我们并不总是想要使用“UTF-8”。我们希望它是可配置的。我们要做的是:

完成,编码已封装且可配置。现在,假设我们想要改变类在空白页面情况下的行为。如果加载了空白页面,我们希望返回"<html/>"。但并非总是如此。我们希望这是可配置的。我们的做法如下:

这个班级变得越来越庞大了,是吗?太好了,我们是优秀的程序员,我们的代码一定要复杂,对吧?代码越复杂,我们就越好的程序员!我是在讽刺。当然不是!但是让我们继续吧。现在我们希望我们的班级无论如何都能继续进行,即使当前平台不支持编码。

课程正在壮大并变得越来越强大!现在是时候引入一个新的类,我们将其称为PageSettings

PageSettings 类基本上只是一个参数的持有者,没有任何行为。它有一些获取器,用于访问这些参数:isEncodeAnyway()isAlwaysHtml()getEncoding()。如果我们继续这个方向,这个类中可能会有几十个配置设置。这看起来非常方便,在 Java 的世界里是一种非常典型的模式。例如,看看 Hadoop 中的 JobConf。这是我们将称之为高度可配置的 Page(假设 PageSettings 是不可变的)的方式。

然而,无论这种方法看起来多么方便,它都是非常错误的。主要是因为它鼓励我们创建大而不连贯的对象。它们变得越来越庞大,难以测试、难以维护和难以阅读。

为了防止这种情况发生,我建议在这里采用一个简单的规则:对象的行为不应该是可配置的。或者更技术性地说,封装属性不能用于改变对象的行为。

对象属性仅用于协调表示真实世界实体的对象的位置。uri是坐标,而alwaysHtml布尔属性是改变行为的触发器。看到区别了吗?

那么,我们应该做什么呢?什么是正确的设计?我们必须使用可组合的装饰器。下面是如何做到的:

这是我们的 DefaultPage 将会展示的样子(是的,我不得不稍微改变了它的设计)。

正如您所看到的,我正在实现接口Page。现在,TextPage装饰器将一个字节数组转换为使用提供的编码的文本。

现在是NeverEmptyPage

最后是 AlwaysTextPage

你可能会说AlwaysTextPage会在不支持的编码情况下对封装的origin进行两次调用,这将导致重复的HTTP请求。这是正确的,这是有意设计的。我们不希望发生这种重复的HTTP往返请求。让我们再引入一个类,用于缓存获取到的页面(不是线程安全的,但现在不重要)。

现在,我们的代码应该是这样的(注意,我现在正在使用OncePage):

这可能是本站迄今为止代码最密集的帖子,但我希望它可读性强,我成功地传达了这个想法。现在我们有五个类,每个类都相当小,易于阅读和重用。

只需遵循一个规则:永远不要使类可配置!

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 13:50

sixnines availability badge   GitHub stars