职责分离

让我们来看看代码中的职责分离包括哪些内容,以便使代码更简洁、可理解、可维护和可扩展。这是 SOLID 原则的第一点。在第二章中,我们是这样定义单一责任原则的: "这意味着代码中的一个类必须只响应一项任务"。

需要提醒的是,SOLID 是一套已知的简洁代码规则,将这些规则结合起来使用,会使你的代码更加清晰和准确。与其试图不折不扣地遵循 SOLID 帽盖所描述的五项原则,更重要的是在编写代码时对所有这些原则有一个整体的认识。

事实上,尊重这一点的第一步就是…​…​命名,正如我们刚刚看到的那样!事实上,通过正确、清晰、最重要的是精确地命名一个类,你就已经确保了它不会变成一个乱七八糟的东西,你可以把你能想到的所有东西都放进去。正因为如此,在命名方法时不要使用过于通用的术语,如 "管理器"(Manager)和 "服务"(Service)。这就导致了一个大问题:如果我们最终有了一个名为 EmailManager 的类,那么显然我们首先想到的就是添加下一个处理电子邮件管理的所有方法。混乱就这样开始了。这就是为什么我们更愿意创建 EmailFactory、AbstractEmailSender 等类,以绝对避免拥有数百个不同方法的类。

我们开始更好地理解单一责任原则。让我们重复一遍:我们的目标不是创建只有一个方法的类。这样做毫无意义。必须巧妙地将其拆分。类的拆分并没有通用的规则。正确的类拆分方法会随着经验的积累自然而然地产生,而且会自己出现。如果对你有帮助,你可以把文件夹看作域,把文件看作子域。在下面的例子中,我们有一个命名为电子邮件的域(或文件夹),以及专门用于特定任务的子域:创建电子邮件、定义使用特定电子邮件提供商发送电子邮件的基类等等。我们还可以进一步划分责任。事实上,有很多工具可以帮助我们毫不费力地解决这个问题。我们将发现(或重新发现!)事件分派。

事件调度

事件分派通常通过 Observer 和 Mediator 设计模式来实现,Symfony 的 EventDispatcher 组件就是这种情况。这些信息仅供参考。事实上,设计模式一开始会显得晦涩难懂,甚至有些可怕。此外,对它们的解释也值得自己写一本书。因此,我们将不谈设计模式,而将这一切通俗化。此外,我们也不会去实现一个事件派发器:我们要做的是了解它如何帮助我们。

简单地说,事件分派的原理就是在某个实体的状态发生变化时,通知所有相关方。各方将通过以下方式通知中央调解人 "我很想知道这个特定事件何时发生,请在事件发生时通知我,因为如果发生了,我还有事情要做"。然后,调解员将保留这一信息。当事件发生时,调解员会查看对事件感兴趣的各方名单,然后说: "事件刚刚发生,做你该做的吧"。更进一步,甚至有可能在必要时,相关各方会宣布一个优先事项,在其它人之前进行。请注意,我们谈论的是同步事件,也就是说,对事件感兴趣的各方将逐个执行,紧随其后,而不是并行执行。异步事件管理则完全不同。

事件分派就是这么简单。但是,它如何帮助我们强化单一责任原则呢?让我们来看一个具体的例子:用户从应用程序中删除了自己的账户。这时,你需要完成以下两项任务:

  • 从数据库中删除帐户

  • 向用户发送最后一封电子邮件以表达悲伤的再见

因此,我们自然会创建一个服务,如 UserRemover,它将连续执行这两项任务。它运行得非常好。UserRemover 是一个明确的名称,它定义了一个非常精确的任务。到目前为止没有问题。然后,有一天,你的应用程序开始流行起来。你想向管理员发送一封电子邮件,通知他们用户已经离开。我们的 UserRemover 类最终删除了数据并发送了两封邮件,两封邮件的内容和收件人都非常明确。

之后,你想让用户删除自己的账户,以遵守《通用数据保护条例》(GDPR),因为你的总部在欧洲。只是,你希望获得准确的统计数据,并保留用户的匿名数据,以尊重他们从你的应用程序中消失的选择,同时有可能利用他们的匿名数据来改进你的产品。这样,你就有了一个 UserRemover 服务,它实际上什么都不会删除,也不会发送电子邮件,或许还有许多其它任务。我们遇到了一个真正的问题:这个类并没有做它应该做的事情,而且它现在很可能已经长达数千行,有几十个方法。

如果从一开始就使用事件分派,情况就会大不一样。下面是一个解决方法的示例。当用户想要离开应用程序时,你将调度一个名为 UserRemovalRequestEvent 的事件。然后,随着应用程序的发展,你将创建对该事件感兴趣的各方:事件监听器。我们将为每个任务创建一个,如下所示:

  • 从数据库中删除数据的监听器

  • 向用户发送再见邮件的监听器

  • 向管理员发送邮件的监听器

匿名化怎么办?没有比这更简单的了:我们也将为这项任务创建一个监听器,并在删除数据时 "拔掉" 监听器。因此,每个任务都有一个类。每个类都有自己独特的职责(向管理员发送电子邮件、匿名化数据等)。如果将来需要添加任务,只需创建一个具有特定任务的新类(或监听器),而无需接触其它类。这样,代码就非常简洁,扩展性极强。类的命名仍然简洁明了。如果某个任务已经过时,只需将其从对相关事件感兴趣的各方列表中删除即可。单一责任原则得到了尊重。

如果想使用现成的事件派发器,我们建议使用 symfony/eventdispatcher 包。这正是框架中用于操作的组件。它非常健壮、高效,而且已经过数年的验证。