在此示例中的php中-但实际上在一般编程中,有没有一种方法可以区分“无分配” 和“未设置值” 命令,用于{合并2个相同类型的不可变数据对象时的{1}}值?
考虑这个php类是一个不变的数据对象。它在构造函数中使用字符串和整数,并且仅提供值的访问器:
null
这些值始终可以是class Data
{
protected $someNumber;
protected $someString;
public function __construct(?int $someNumber, ?string $someString)
{
$this->someNumber = $someNumber;
$this->someString = $someString;
}
public function getSomeNumber(): ?int
{
return $this->someNumber;
}
public function getSomeString(): ?string
{
return $this->someString;
}
}
或它们各自的数据类型null
或string
。此外,构造函数接受integer
的值,而不是null
和/或string
的值:UNSET操作。
现在,我希望能够合并int
的2个实例,类似于这种简化的工厂方法,它接受Data
和$first
,其中$second
中的数据将覆盖数据在$second
中(如果有)。
$first
在上面的示例中,由class DataFactory
{
public function merge(Data $first, Data $second): Data
{
// Uses data from $first if corresponding data from
// $second is (strictly) null
return new Data(
$second->getSomeNumber() ?? $first->getSomeNumber(),
$second->getSomeString() ?? $first->getSomeString(),
}
}
的访问者返回的null
的值被解释为NO UPDATE操作:遇到$second
时,将保留null
的对应值。问题是我希望能够区分在$first
中对NO UPDATE操作还是UNSET操作的请求。
在merge
类中进行严格键入将不允许使用诸如Data
之类的字符串常量作为标记值,因此直接在数据本身上实现它似乎是不可能的。甚至更是如此,因为为任何值传递构造函数null绝对应该意味着SET NULL。
我正在考虑某种更新镜头,该镜头明确指定合并时应设置为UNSET的属性,因此"DATA_UNSET_FIELD"
中的null
值将仅表示没有更新(从{{1}保留) }。
解决这个问题的紧凑的面向对象模式是什么?我已经可以想象到像随着数据增长而爆炸纯数组模式或策略类的类爆炸那样的问题。另外,我有点担心$second
对象的“移动性”,因为在某个时候必须将新对象与它们关联。
谢谢!
修改
我希望能够在合并两个以下实例时区分不覆盖当前值和取消设置一个值(即分配$first
) Data
,其中null
是基础,而Data
覆盖$first
的数据。详细来说,合并会产生第三个新对象,即合并结果。
查看$second
中的$first
片段DataFactory
值当前被解释为“保持null
的相应值”。但是,如何在每个字段中携带另一个标志,以指示干净的方式在结果对象中将哪些字段设置为$second
,而又不会过多地干扰数据类呢?
答案 0 :(得分:1)
PHP无法区分未分配变量和空变量。 这使得不可避免地要跟踪应该覆盖哪些属性。
我看到您有两个问题:
Data
不可变Data
的界面整洁(例如,强制使用严格类型) \stdClass
对象是能够跟踪“已定义”和“未定义”属性的最简单的数据结构之一(但数组也很好)。
通过将merge()
方法移到Data
类中,您将能够隐藏任何实现细节-保持界面清洁。
一个实现可能看起来像这样:
final class Data {
/** @var \stdClass */
protected $props;
// Avoid direct instantiation, use ::create() instead
private function __construct()
{
$this->props = new \stdClass();
}
// Fluent interface
public static function create(): Data
{
return new self();
}
// Enforce immutability
public function __clone()
{
$this->props = clone $this->props;
}
public function withSomeNumber(?int $someNumber): Data
{
$d = clone $this;
$d->props->someNumber = $someNumber;
return $d;
}
public function withSomeString(?string $someString): Data
{
$d = clone $this;
$d->props->someString = $someString;
return $d;
}
public function getSomeNumber(): ?int
{
return $this->props->someNumber ?? null;
}
public function getSomeString(): ?string
{
return $this->props->someString ?? null;
}
public static function merge(...$dataObjects): Data
{
$final = new self();
foreach ($dataObjects as $data) {
$final->props = (object) array_merge((array) $final->props, (array) $data->props);
}
return $final;
}
}
$first = Data::create()
->withSomeNumber(42)
->withSomeString('foo');
// Overwrite both someNumber and someString by assigning null
$second = Data::create()
->withSomeNumber(null)
->withSomeString(null);
// Overwrite "someString" only
$third = Data::create()
->withSomeString('bar');
$merged = Data::merge($first, $second, $third); // Only "someString" property is set to "bar"
var_dump($merged->getSomeString()); // "bar"