Must constructors of value objects not do work, even when class invariants prescribe so?

Today I had a discussion with a colleague.

It is my understanding that a class has the responsibility to ensure that its objects have a valid state when interacted with from outside the class. The reason for this rule is that the class does not know who its users are and that it should predictably fail when it is interacted with in an illegal manner. In my opinion that rule applies to all classes, including to immutable, somewhat primitive objects, that do little more than holding primitive values.

In the specific situation where I had a discussion today, the constructor of such a ValueObject had to do some work (it had to call a couple of getters) in order to acquire the values it needed for a valid state. The correlation between the computed results is a condition of the ValueObject’s class invariants, i.e. they must have the same source.

This is an illustration of the class (php code example):

class Example {

    private $someValue;
    private $computedResultA;
    private $computedResultB;

    public function __construct($someValue, $someDependency) {
        $this->someValue = $someValue;
        $this->computedResultA = $someDependency->computeResultA();
        $this->computedResultB = $someDependency->computeResultB();
    }
}

My colleague argues that it is better to compute the results in the class that creates an instance of the ValueObject and pass these primitive results to that ValueObject’s constructor. After all, he reasons, constructors must not do work. Additionally, the test code would become larger if a mock had to be provided to the ValueObject’s constructor – something which is not necessary when simply primitive values are provided. And apparently testing code is more important than tested code.

This is an illustration of the code he prefers:

class Example {

    private $someValue;
    private $computedResultA;
    private $computedResultB;

    public function __construct($someValue, $computedResultA, $computedResultB) {
        $this->someValue = $someValue;
        $this->computedResultA = $computedResultA;
        $this->computedResultB = $computedResultB;
    }
}

Can anyone explain why constructors must not do work? Google tells me that constructors should not cause side-effects. But then what about
(Value)Objects that explicitly embody some kind of computation result? Should that computation result always be processed in a factory instead, and only then be passed to the value object? Doesn’t that fail to maintain the object’s integrity constraints?

4

Can anyone explain why constructors must not do work?

Because constructors have limited mechanisms to handle error cases. You need to return an object of that type or throw an exception. You can’t return a different type. You can’t signal failure. You can’t return null. You (almost always) just get to throw an exception, making your callers deal with it.

But then what about (Value)objects that explicitly embody some kind of computation result?

If those computations are reasonably fast, and only fail in exceptional scenarios then it’s excusable. Sometimes implicitly doing those computations makes the code cleaner by enforcing those hard invariants via a simplified interface.

But as a general guideline – constructors should not do work.

Should that computation result always be processed in a factory instead, and only then be passed to the value object? Doesn’t that fail to maintain the object’s integrity constraints?

Always is probably too strong, but yes, they generally should. In some languages you can provide a static function to create the object that uses a private constructor. This maintains your object’s invariance while allowing you to handle error conditions more gracefully.

5

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *