使用通知模式进行域验证

Unf*_*lux 6 php validation design-patterns domain-driven-design

从历史上看,我在对象的构造函数中对对象进行了验证,并在验证失败时抛出异常。例如:

class Name
{
    const MIN_LENGTH = 1;

    const MAX_LENGTH = 120;

    private $value;

    public function __construct(string $name)
    {
        if (!$this->isValidNameLength($name)) {
            throw new InvalidArgumentException(
                sprintf('The name must be between %d and %d characters long', self::MIN_LENGTH, self::MAX_LENGTH)
            );
        }
        $this->value = $name;
    }

    public function changeName(string $name)
    {
        return new self($name);
    }

    private function isValidNameLength(string $name)
    {
        return strlen($name) >= self::MIN_LENGTH && strlen($name) <= self::MAX_LENGTH;
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然我喜欢这种方法,因为我的对象负责强制执行其一致性并确保它始终有效,但我从未对异常的使用过于热衷。虽然有人会争论或反对使用上述异常,但它确实限制了我在对多个对象执行验证时可以返回的验证消息的数量。例如:

class Room
{
    private $name;

    private $description;

    public function __construct(Name $name, Description $description)
    {
        $this->name = $name;
        $this->description = $description;
    }
}

class Name
{
    public function __construct(string $name)
    {
        // do some validation
    }
}

class Description
{
    public function __construct(string $description)
    {
        // do some validation
    }
}
Run Code Online (Sandbox Code Playgroud)

如果 和 都Name验证Description失败,我希望能够返回两个对象的失败消息,而不仅仅是首先失败的对象的单个异常。

在阅读了一些通知模式后,我觉得这非常适合我的场景。我陷入困境的是如何执行验证并防止我的对象在验证失败时进入无效状态。

class Name
{
    const MIN_LENGTH = 1;

    const MAX_LENGTH = 120;

    private $notification;

    private $value;

    public function __construct(string $name, Notification $notification)
    {
        $this->notification = $notification;
        $this->setName($name);
    }

    private function setName(string $name)
    {
        if ($this->isValidNameLength($name)) {
            $this->value = $name;
        }
    }

    private function isValidNameLength(string $name)
    {
        if (strlen($name) < self::MIN_LENGTH || strlen($name) > self::MAX_LENGTH) {
            $this->notification->addError('NAME_LENGTH_INVALID');
            return false;
        }
        return true;
    }

    public function hasError()
    {
        return $this->notification->hasError();
    }

    public function getError()
    {
        return $this->notification->getError();
    }
}
Run Code Online (Sandbox Code Playgroud)

对于以上的情况,我有几个顾虑:

  1. 如果验证失败,则仍会构造该对象,但其状态$value不是null有效的。
  2. 创建后Name,我必须记得调用hasError以确定是否发生验证错误。
  3. 我现在正在使用hasError/getError函数来处理我的域对象,我不确定这是否是一个好的做法。

我是否遗漏了这个拼图的一块?我如何利用通知模式但确保我的对象不会进入无效状态?

Voi*_*son 2

我如何利用通知模式但确保我的对象不会进入无效状态?

工厂模式 - 又名“命名构造函数”

您保留构造函数验证 - 您永远不想创建一个无法保留其自身不变量的值。

但是您将构造函数从公共 API 中取出,而是安排客户端代码调用工厂上的方法。该工厂需要决定如何管理构建对象的失败 - 收集所有通知,然后

  • 引发包含通知集合的异常
  • 返回包含通知集合的失败类型

取决于您是否喜欢处理带有异常或受歧视联合的控制流。