【发布时间】: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 对状态对象所做的更改也会保存到数据库中。这是一个问题。
我应该怎么做才能避免这个问题?
我有两种方法可以解决这个问题,但是它们都有很大的问题。
- 如果 mutator 无法更改其内部任何位置的状态对象,它应该恢复它已经完成的更改。但在某些情况下很难做到,甚至是不可能做到的。
- mutator 应该克隆状态,改变它的副本并返回它。但在这种情况下,该方法将需要更多内存(状态的属性中可以有超过 1000 个对象)。
可能有人知道吗?
【问题讨论】:
-
我认为这个“mutator”不应该修改
$state,如果它的工作只是返回一个更新的状态。你有理由要在mutate()中修改$state吗? -
是的,我同意你的看法。所以我问我该怎么做?要返回一个新状态,mutator 应该克隆一个源状态。但是需要大量的内存。我寻找另一种方法来做到这一点。
-
我不明白你为什么需要
mutate()来返回任何东西。不能只是void吗?无论如何,您都没有使用它的返回值,只是再次将其分配给$grid->state,如果对象已经发生了变异,那是没用的,对吧? -
克隆似乎是解决这个问题的正确方法。我也认为
State应该是不可变的,以保证一个给定的状态永远不会被改变。内存有这么大吗?你说的是“因为 PHP 通过引用传递对象”,但如果没有,克隆它们就是它无论如何都会做的事情。 -
这里还有一个想法 - 你可以创建一个 StateChangeHistory 对象来跟踪 State 发生了什么,这样你就可以恢复所做的任何更改?
标签: php unit-testing architecture immutability