ValueObject

时间:2017-07-17 07:26:39

标签: php oop inheritance domain-driven-design abstract-class

我正在尝试使用ValueObject学习编码。我有关于VO的抽象实现的问题,而子类扩展了它。孩子们只会实现给定值的验证方法(电子邮件,用户名等)。 我的第一个VO看起来在下面是业务规则,这些规则在构造函数中进行验证。

final class Email {
    private $email;

    public function __construct(string $email)
    {
        $this->validateEmail($email);

        $this->email = $email;
    }

    public function value() : string
    {
        return $this->email;
    }

    private function validateEmail(string $email) : void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new IncorrectEmailException();
        }
    }
}

当我写第二个VO时,我发现了一些模式,其中不同的只是验证规则(方法validate)。所以我想到了一些抽象类实现模式,让child改进验证规则。

abstract class ValueObject {

    protected $value;

    public function __construct($value)
    {
        $this->validate($value);

        $this->value = $value;
    }

    public function value()
    {
        return $value;
    }

    abstract protected function validate($value) : void;
}

final class Email extends ValueObject {
    protected function validate($value) : void
    {
        if (!is_string($value)) {
            throw new \InvalidArgumentException();
        }

        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new IncorrectEmailException();
        }
    }
}

我输掉了一些方法和输入或输出(例如方法'值')。 PHP没有泛型类型,我必须验证像string这样的值类型。我不确定它是否适合这样做,因为它不是商业规则(我认为,但我可能是错的)有人可以告诉我哪个选项更好并且与良好做法兼容吗?

2 个答案:

答案 0 :(得分:5)

不要对不相关的概念使用继承。价值对象不是现实世界的概念。这是一种用于程序的技术。

没有必要设置一个抽象基类ValueObject,然后由EmailPhoneNumberPostalAddress a.s.o扩展。

为每个概念编写一个类并保持简单。例如,不需要单独的验证方法。将验证放在构造函数中,实现__toString()(如果适用)和/或格式化封装值的其他方法,应该是全部。

例如:

final class EmailAddress
{
    private $email;

    public function __construct(string $email)
    {
        if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(sprintf("Invalid email '%s'", $email));
        }

        $this->email = $email;
    }

    public function __toString() : string
    {
        return $this->email;
    }

    // For some statistics
    public function getDomain() : string
    {
        // write code here to return only the part after '@'
    }
}

答案 1 :(得分:4)

我不会这样做有几个原因:

  1. 这只适用于构造函数中只有一个参数的值对象
  2. 构造函数中的参数不会遵循普遍存在的语言
  3. 的命名
  4. 吸气者不会遵循无处不在的语言命名
  5. 您将不会对构造函数中的参数进行类型提示,因为它们对于基类是未知的
  6. 你应该避免域对象的继承,除非它是绝对必要的(它几乎从来没有),因为它将你的域对象耦合到其他类并且程序员的认知努力更高:必须知道基类的顺序了解儿童班;我尝试favor composition over inheritance
  7. 你因为没有在你的构造函数中调用validate()而放弃了所有这些。