据说 "static methods are death to testability" 。如果是这样,下面的可行替代模式是什么?
class User {
private $phone,
$status = 'default',
$created,
$modified;
public function __construct($phone) {
$this->phone = $phone;
$this->created = new DateTime;
$this->modified = new DateTime;
}
public static function getByPhone(PDO $pdo, $phone) {
$stmt = $pdo->prepare('SELECT * FROM `users` WHERE `phone` = :phone');
$stmt->execute(compact('phone'));
if (!$stmt->rowCount()) {
return false;
}
$record = $stmt->fetch(PDO::FETCH_ASSOC);
$user = new self($record['phone']);
$user->status = $record['status'];
$user->created = new DateTime($record['created']);
$user->modified = new DateTime($record['modified']);
return $user;
}
public function save(PDO $pdo) {
$stmt = $pdo->prepare(
'INSERT INTO `users` (`phone`, `status`, `created`, `modified`)
VALUES (:phone, :status, :created, :modified)
ON DUPLICATE KEY UPDATE `status` = :status,
`modified` = :modified');
$data = array(
'phone' => $this->phone,
'status' => $this->status,
'created' => $this->created->format('Y-m-d H:i:s'),
'modified' => date('Y-m-d H:i:s')
);
return $stmt->execute($data);
}
...
}
这只是一个简化的例子。这个类有更多的方法和属性,并且在写入数据库时有更多的验证等。这个类背后的指导设计原则是它将用户建模为一个对象。对象的某些属性在创建后无法修改,例如电话号码(充当主 ID)、用户创建日期等。其他属性只能根据严格的业务规则进行更改,这些规则都具有严格验证的 setter 和 getter。
对象本身并不代表数据库记录,数据库仅被视为一种永久存储的可能形式。因此,数据库连接器并未存储在对象中,而是需要在每次对象需要与数据库交互时注入(inject)。
创建新用户时,这看起来像:
$user = new User('+123456789');
当现有用户从永久存储中恢复时,看起来像:
$pdo = new PDO('...');
$user = User::getByPhone($pdo, '+123456789');
如果我认真对待“可测试性已死”这条线,这被认为是糟糕的。不过,我完全能够测试这个对象,因为它是完全依赖注入(inject)的,并且 static
方法没有状态。我怎样才能以不同的方式做到这一点并避免使用 static
方法?或者更确切地说,在这种情况下,究竟是什么反对 static
?是什么让 static
方法的这种特殊用途如此难以测试?
最佳答案
这主要是(我的观点)the chat that ensued between me and @zerkms 的摘要:
争论点其实是这样的:
public function doSomething($id) {
$user = User::getByPhone($this->pdo, $id);
// do something with user
return $someData;
}
这使得测试 doSomething
变得困难,因为它硬编码了 User
类,它可能有也可能没有很多依赖关系。但这实际上与使用非静态方法实例化对象是一样的:
public function doSomething($id) {
$user = new User;
$user->initializeFromDb($this->pdo, $id);
// do something with user
return $someData;
}
我们没有使用静态方法,但它仍然是不可模拟的。事实上,情况变得更糟。
答案是使用工厂:
public function doSomething($id) {
$user = $this->UserFactory->byPhone($id);
// do something with user
return $someData;
}
现在可以对工厂进行依赖注入(inject)和模拟,并且 User
类不再是硬编码的。您可能会或可能不会认为这有点矫枉过正,但它确实提高了可模拟性。
尽管这个工厂可以很好地使用静态方法实例化实际的用户对象,但这并没有改变这个事实:
public function byPhone($id) {
return User::getByPhone($this->db, $id);
}
这里使用静态方法或常规构造函数没有区别。
$user = new User($db, $id);
$user = User::getByPhone($db, $id);
两个表达式都返回 User
的实例,并且都“硬编码”了 User
类。无论如何,这只需要在某个时候发生。
对于我的用例,static
构造函数方法对对象最有意义。正如所证明的那样,static
方法不是问题所在。在哪里调用它们是争论的焦点,而不是它们是否存在。而且我还没有看到不使用静态构造函数的令人信服的论据,因为它们可以包装在工厂中,这减轻了可模拟性的任何问题,就像它对常规对象实例化所做的一样。
关于php - "Static methods are death to testability"- 替代构造函数的替代品?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8966434/