我有一个问题,当可以通过多种方式创建对象时,最好的方法是什么。
假设我们有:
class User {
private $id;
private $name;
private $surname;
private $login;
public function setId($id) {
$id = (int) $id;
if (!$id) {
throw new Exception("Wrong ID");
}
$this->id = $id;
}
public function setName($name) {
$this->name = trim($name);
if (strlen($name) < 5) {
throw new Exception("Name is to short");
}
}
public function setSurname($surname) {
$this->surname = trim($surname);
if (strlen($surname) < 5) {
throw new Exception("Surname is to short");
}
}
public function setLogin($login) {
/* some login validation */
$this->login = $login;
}
/* and other methods */
}
不,我可以通过多种方式使用它:
<强>1。创建新用户并将其保存在数据库中:
$User = (new User)
->setName("John")
->setSurname("Kent")
->setLogin("JohnK");
$UserSaver = (new UserSaver($User))->save();
<强>2。从数据库加载用户
class UserFactory {
public function createById($userId, $db) {
$row = $db->GetRow("SELECT * FROM users WHERE u_id = {$db->qstr($userId}");
$User = (new User)
->setId($row['id'])
->setName($row['name'])
->setSurname($row['surname'])
->setLogin($row['login']);
return $User;
}
}
$User = UsersFactory::createByID(7, $db);
但我的问题是关于 setter 中的验证。它应该在那里吗?
从数据库中获取记录时可能会导致问题,例如登录时间太短(由其他应用程序存储)。然后将抛出一个 Exception
。
我可以:
将验证移至单独的函数 $this->validate(),但我认为更好的做法是尽快检测错误数据。
通过添加第二个参数修改 setter :函数 setLogin($login, $validate=true) 然后在 UsersFactory 中执行 (new User)->setLogin($row['login' ], 假)
使用工厂的继承 insted 来跳过 setter。
像这样:
class UserFromDB extends User () {
public function __construct($userId, $db) {
$row = $db->GetRow("SELECT * FROM users WHERE u_id = {$db->qstr($userId}");
$this->id = $row['id'];
$this->name = $row['name'];
$this->surname = $row['surname'];
$this->login = $row['login'];
}
}
对于最佳解决方案的建议,我将不胜感激。
最佳答案
以下是按个人喜好顺序排列的一些选项。
1。 在您的数据库上执行您的策略。
您的数据库是否包含不符合您的政策的数据?如果是这样,那么为什么不执行该策略并清理您的数据库。检测所有不符合策略的数据,并提出一些迁移计划。这解决了您的问题并同时清理了您的数据。如果出于某种原因您不想这样做,您可能想问问自己您的政策是否真的有用。
假设以上是不可能的,我会推荐以下两种方法之一:
2。 CQRS 方法
命令查询责任分离模式是避免此类问题的一种非常简洁的方法。基本思想是创建一个用于数据库写入的类和一个用于数据库读取的类。这意味着您必须在用于读取的现有用户类旁边创建另一个类(UserReadModel 在 CQRS 术语中是一个合适的名称)。
CQRS 允许您编写健壮的(领域驱动的)代码。例如,而不是有一个整数 id。您可能会创建一个只能包含有效 ID(例如正整数)的 Id 类。因此,您可以将一个 Id id 传递给用户名,而不是传递一个整数 id。然后,您不再需要在聚合级别或使用单独的验证器进行验证,无效的 Id 也不是一个选项。因此,其他类可以从相同的验证中受益。传递 Id 而不是整数,您的代码不会出错。当然,您的阅读模型仍然可以使用整数。
如果您想了解更多信息,请阅读有关域驱动设计、不可变类和 CQRS 的内容。这些技术适当结合可以成为非常健壮的代码的基础。
3。 验证方法方法。
你的第一个建议。这种方法允许您保留代码,但两者兼顾。然而,问题是它不会强制您的类的用户调用验证方法。你当然可以强制你的数据库层在持久化之前总是在所有类上调用这个方法,但通常在这个过程中有点太晚了。此外,您将需要一些方法来从验证错误中推断出哪些字段有问题。
如果对一个属性而不是下一个属性跳过验证,则您的验证参数方法允许您的对象变为半有效。因此,您仍然需要一个验证方法来确保该类完全有效。因此,它是同一方法的变体。这种方法还有一个问题:您不能确保所有的 setter 方法(将来)都会有这个 bool 参数。这使得很难一概而论。
一种简洁的方法是将用户验证器类注入(inject)到您的用户类中。然后 validate 方法将验证委托(delegate)给验证器类。为了确保用户类始终具有验证器,而不是直接使用 new 关键字创建用户类,您可以使用一个工厂来创建具有适当验证器类的用户类。您可以对数据库插入和更新使用严格验证器,对数据库选择使用弱验证器或根本没有验证器。这种方法的一个优点是这种模式可重用于其他类。
如果您希望在调用 setter 时进行验证,您可以创建一组验证器。这意味着您可以将“名称验证器”绑定(bind)到“用户验证器”中的姓氏和名字属性。因此,用户验证器将成为字段验证器的集合。这将允许您在设定的时刻触发特定字段或输入的验证。
关于oop - 从数据库创建对象/加载 - 工厂模式和 setter 中的验证?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20435366/