我正在尝试通过遵循最新的 OOP 设计模式来构建自己的 MVC 框架(当然是为了更好地学习它)。我想知道,放置可重复代码的最佳做法是什么(用于作为静态方法保留在实用程序类中,这被认为不是一个好的模式)。
例如,我们想使用点分隔字符串遍历一个多维数组,我必须在几个类(它们是其他基类的子类)中使用这个算法。我如何在不使用实用程序类且不多次重复相同代码的情况下做到这一点?
我正在尝试通过遵循最新的 OOP 设计模式来构建自己的 MVC 框架(当然是为了更好地学习它)。我想知道,放置可重复代码的最佳做法是什么(用于作为静态方法保留在实用程序类中,这被认为不是一个好的模式)。
例如,我们想使用点分隔字符串遍历一个多维数组,我必须在几个类(它们是其他基类的子类)中使用这个算法。我如何在不使用实用程序类且不多次重复相同代码的情况下做到这一点?
如果这些是实用函数,则在单独的命名空间中定义它们。类似于
<?php
namespace Utils;
function array_query($array, $query) {
// code for traversing the array
}
把它们放在一个或多个文件中,你会没事的。请记住将该文件包含在应用程序的 boostrap 阶段。
底线:停止滥用静态类,我们现在有那个 sh*t 的命名空间。
但是,实际上并非所有您认为的“实用功能”都是如此。一些代码,如果你开始使用 OOP 代码,应该放在相关的类中。例如,“电子邮件验证”不应该放在“实用功能”中,而应该放在一个类中:
class EmailAddress {
private $emailAddress;
public function __construct($emailAddress) {
$this->assertValidEmailAddress($emailAddress);
$this->emailAddress = $emailAddress;
}
private function assertValidEmailAddress($emailAddress) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new DomainException("Not an email address");
}
}
public function __toString() {
return $this->emailAddress;
}
}
并且这些重复的“域逻辑”片段应该放在单独的实体中,然后您可以为其他类提供类型提示。然后你在某处使用它:
public function register(EmailAddress $email, SafePassword $password): User
{
// your user registration logic
}
这样,您的各种服务可以执行活动,并try-catch
用于改进验证。
PS
你可能需要仔细看看你在做什么。那个虚线访问实用程序很简洁(我在 10 年前也有它),但实际上是针对更深层次问题的“临时修复”:您不应该处理如此深的数组,您需要简化对它们的访问。
实用程序类没有任何问题,只是不要将所有不相关的实用程序功能集中到一个巨大的类中。按它们的作用将它们分开(和命名空间)。例如,请参阅Zend 过滤器或Symfony 文件系统。
或者,如果需要这个函数的类都有一个共同的父类,你可以把这个函数放在最顶层的类或抽象中。
或者,如果这些类没有共同的父类,您可以使用称为extractArrayFromDottedString()
或类似的方法创建一个 Trait。
Laravel 通过定义独立的“帮助”函数来做到这一点。CakePHP 和 Yii 通过使用静态方法定义容器实用程序类(即“Text”或“Xml”)来做到这一点。编程语言做类似的事情(即 PHP 的implode()
、Java 的Math.round
、C 的strcpy
、Python 的sum()
等)。几乎所有东西都使用独立函数或静态类方法。
最终,最好的选择是主观的。这取决于你想如何组织事物。研究 PHP 中的常见设计模式,感受不同框架在实践中的感受。然后选择一种方法并保持一致。
实用程序类是一种反模式
真的不。
在 OOP 中,使用实用程序类设计整个应用程序很可能是一种反模式,但使用static
提供常规/横向任务的方法定义实用程序类可能是有意义的。
不将实用程序方法与使用它们的现有类的方法混合也可以为实用程序类和使用者类提供更好的内聚性和可重用性。
当然,作为替代方案,您可以使用实例方法定义一个类。
这是有效的,更冗长,但具有提高类可测试性的优势。如果您需要模拟调用或切换到实用方法的其他实现,则必须优先使用实例方法。