【问题标题】:What is the right way to mutate objects to avoid side effects?改变对象以避免副作用的正确方法是什么?
【发布时间】:2020-11-15 05:28:29
【问题描述】:

我有下一个对象系统(简单示例):

class Grid
{
    public State $state;
    // Other fields

    public function __construct(State $state)
    {
        $this->state = $state;
    }

    // ...
}

class State
{
    public bool $isCompleted = false;
    public ?User $judge;
}

class User
{

}

免责声明:Grid 类是一个遗留的 ActiveRecord 模型,它不能被独立的单元测试覆盖,因为它会写入数据库并更改系统中的一些其他数据。所以我只对State类感兴趣。

我需要一个 State 的 mutator 类。它必须易于测试。它看起来像这样:

class StateMutator
{
    public function mutate(State $state, array $changes):?State
    {
        // ...
        $state->isCompleted = true;
        // ...
        if(!$someCondition){
            return null;
        }
        // ...
        return $state;
    }
}

而且是这样使用的:

/** @var Grid $grid */
/** @var array $changes */
$newState = (new StateMutator())->mutate($grid->state, $changes);
if($newState !== null){
    $grid->state = $newState;
}
// Some other changes in $grid
$grid->saveChanges();

看起来不错。但有些事情让我感到困惑。如果 mutator 对获取的对象进行了一些更改并在此之后返回 null,那么调用代码将认为 State 没有更改 - 对其进行一些其他更改并将其保存到数据库中。但是由于 PHP 通过引用传递对象,mutator 对状态对象所做的更改也会保存到数据库中。这是一个问题。

我应该怎么做才能避免这个问题?

我有两种方法可以解决这个问题,但是它们都有很大的问题。

  1. 如果 mutator 无法更改其内部任何位置的状态对象,它应该恢复它已经完成的更改。但在某些情况下很难做到,甚至是不可能做到的。
  2. mutator 应该克隆状态,改变它的副本并返回它。但在这种情况下,该方法将需要更多内存(状态的属性中可以有超过 1000 个对象)。

可能有人知道吗?

【问题讨论】:

  • 我认为这个“mutator”不应该修改$state,如果它的工作只是返回一个更新的状态。你有理由要在mutate() 中修改$state 吗?
  • 是的,我同意你的看法。所以我问我该怎么做?要返回一个新状态,mutator 应该克隆一个源状态。但是需要大量的内存。我寻找另一种方法来做到这一点。
  • 我不明白你为什么需要mutate() 来返回任何东西。不能只是void吗?无论如何,您都没有使用它的返回值,只是再次将其分配给$grid->state,如果对象已经发生了变异,那是没用的,对吧?
  • 克隆似乎是解决这个问题的正确方法。我也认为State 应该是不可变的,以保证一个给定的状态永远不会被改变。内存有这么大吗?你说的是“因为 PHP 通过引用传递对象”,但如果没有,克隆它们就是它无论如何都会做的事情。
  • 这里还有一个想法 - 你可以创建一个 StateChangeHistory 对象来跟踪 State 发生了什么,这样你就可以恢复所做的任何更改?

标签: php unit-testing architecture immutability


【解决方案1】:

我通常会避免对象进入无效状态。您的 State 对象应该具有改变其状态或返回具有新状态的克隆的方法。这些方法仅在结果状态有效时验证输入并变异/返回克隆。无需跟踪更改和回滚。

【讨论】:

    猜你喜欢
    • 2017-04-28
    • 2015-09-28
    • 2014-03-28
    • 2014-10-18
    • 1970-01-01
    • 1970-01-01
    • 2016-06-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多