如何区分 NULL 值的 "unset" 和 "no-assignment"?
How to distinguish between "unset" and "no-assignment" for NULL values?
在这个例子中的php中——但实际上在一般编程中,有没有办法区分"no-assignment"和"unset-value" 合并 2 个相同类型的不可变数据对象时 null
值的命令?
考虑这个 php class 这是一个不可变的数据对象。它在其构造函数中接受一个字符串和一个整数,并且只为值提供访问器:
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
和/或 int
:UNSET 操作。
现在我希望能够合并 Data
的 2 个实例,类似于接受 $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(),
}
}
在上面的示例中,$second
的访问器返回的 null
值被解释为 NO UPDATE 操作:当遇到 null
时,对应的 $first
的值保持。问题是我希望能够区分 merge
.
内的 NO UPDATE 操作或 UNSET 操作请求
Data
class 中的严格类型不允许使用某种字符串常量,如 "DATA_UNSET_FIELD"
作为标记值,因此直接在数据本身上实现它似乎不可能的。甚至因为为任何值传递构造函数 null 肯定意味着 SET NULL。
我正在考虑某种更新镜头,它明确指定合并时应该取消设置的属性,这样 $second
中的 null
值就意味着没有更新(保持 $first
).
什么是紧凑的面向对象模式来解决这个问题?我已经可以想象像随着数据增长爆炸普通数组模式或 class 策略 classes 爆炸这样的问题。此外,我有点担心 Data
对象的 "mobility",因为新对象必须在某些时候与它们相关联。
提前致谢!
编辑
我希望能够区分不覆盖当前值和取消设置一个值——即分配null
– 合并 Data
的 2 个实例时,其中 $first
是基数,$second
覆盖 $first
的数据。作为一个细节,合并结果是第三个新对象,即合并结果。
查看 DataFactory
片段 null
当前 $second
中的值被解释为 "keep the corresponding value of $first
"。但是我如何为每个字段携带另一个标志,指示在结果对象中应该将哪些字段设置为 null
,以一种干净的方式,并且不会过多地混淆数据 class?
PHP 无法区分未赋值变量和空变量。
这使得跟踪哪些属性应该被覆盖变得不可避免。
我看到你有两个顾虑:
- 保持
Data
不变
- 保持
Data
的界面干净(例如强制执行严格类型)
能够跟踪 "defined" 和 "undefined" 属性的最简单的数据结构之一是 \stdClass
对象(但数组也非常好)。
通过将 merge()
方法移动到 Data
class 中,您将能够隐藏任何实现细节 - 保持界面整洁。
实现可能如下所示:
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"
在这个例子中的php中——但实际上在一般编程中,有没有办法区分"no-assignment"和"unset-value" 合并 2 个相同类型的不可变数据对象时 null
值的命令?
考虑这个 php class 这是一个不可变的数据对象。它在其构造函数中接受一个字符串和一个整数,并且只为值提供访问器:
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
和/或 int
:UNSET 操作。
现在我希望能够合并 Data
的 2 个实例,类似于接受 $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(),
}
}
在上面的示例中,$second
的访问器返回的 null
值被解释为 NO UPDATE 操作:当遇到 null
时,对应的 $first
的值保持。问题是我希望能够区分 merge
.
Data
class 中的严格类型不允许使用某种字符串常量,如 "DATA_UNSET_FIELD"
作为标记值,因此直接在数据本身上实现它似乎不可能的。甚至因为为任何值传递构造函数 null 肯定意味着 SET NULL。
我正在考虑某种更新镜头,它明确指定合并时应该取消设置的属性,这样 $second
中的 null
值就意味着没有更新(保持 $first
).
什么是紧凑的面向对象模式来解决这个问题?我已经可以想象像随着数据增长爆炸普通数组模式或 class 策略 classes 爆炸这样的问题。此外,我有点担心 Data
对象的 "mobility",因为新对象必须在某些时候与它们相关联。
提前致谢!
编辑
我希望能够区分不覆盖当前值和取消设置一个值——即分配null
– 合并 Data
的 2 个实例时,其中 $first
是基数,$second
覆盖 $first
的数据。作为一个细节,合并结果是第三个新对象,即合并结果。
查看 DataFactory
片段 null
当前 $second
中的值被解释为 "keep the corresponding value of $first
"。但是我如何为每个字段携带另一个标志,指示在结果对象中应该将哪些字段设置为 null
,以一种干净的方式,并且不会过多地混淆数据 class?
PHP 无法区分未赋值变量和空变量。 这使得跟踪哪些属性应该被覆盖变得不可避免。
我看到你有两个顾虑:
- 保持
Data
不变 - 保持
Data
的界面干净(例如强制执行严格类型)
能够跟踪 "defined" 和 "undefined" 属性的最简单的数据结构之一是 \stdClass
对象(但数组也非常好)。
通过将 merge()
方法移动到 Data
class 中,您将能够隐藏任何实现细节 - 保持界面整洁。
实现可能如下所示:
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"