代码之家  ›  专栏  ›  技术社区  ›  JJWesterkamp

如何区分空值的“unset”和“no assignment”?

  •  3
  • JJWesterkamp  · 技术社区  · 7 年前

    在本例中的php中,但实际上在一般编程中,是否有方法区分 “无转让” “未设置值” 命令 null 合并同一类型的两个不可变数据对象时的值?

    考虑这个php类,它是一个不可变的数据对象。它在构造函数中接受一个字符串和一个整数,并且只为值提供访问器:

    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;
        }
    }
    

    值可以是 无效的 或它们各自的数据类型 string integer 任何时候。构造器也接受 无效的 值而不是 一串 和/或 int :未设置操作。

    现在我希望能够合并 Data ,类似于接受 $first $second ,其中数据位于 $秒 覆盖中的数据 $第一 ,如果存在。

    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(),
        }
    }
    

    在上面的例子中, 无效的 的访问器返回的值 $秒 被解释为无更新操作:当 无效的 遇到相应的值 $第一 被保留。问题是,我希望能够区分请求中的无更新操作或未设置操作 merge .

    严格的打字 数据 类不允许使用某种字符串常量,如 "DATA_UNSET_FIELD" 作为一个要标记的值,因此直接在数据本身上实现这一点似乎是不可能的。更甚者,因为为任何值传递构造函数null肯定意味着SET null。

    我正在考虑某种类型的更新镜头,它显式地指定合并时应该取消设置的属性,以便 无效的 价值观 $秒 只意味着没有更新 $第一 ).

    解决这个问题的紧凑的面向对象模式是什么?我已经可以想象一些问题,比如随着数据的增长,展开普通数组模式或策略类的类爆炸。我也有点担心 数据 对象作为新对象必须在某个点与它们关联。

    提前谢谢!


    编辑

    我想能够区分 不重写 当前值和 不稳定 一个值,即赋值 无效的 –当合并2个 数据 ,其中 $第一 是基地,而且 $秒 覆盖的数据 $第一 . 作为一个细节,合并会产生第三个新对象,即合并结果。

    看着 DataFactory 片段 无效的 价值观 $秒 当前被解释为“保持 $第一 ". 但是,如何为每个字段携带另一个标志,指示应将哪些字段设置为 无效的 在生成的对象中,以一种干净的方式,并且不会过多地干扰数据类?

    1 回复  |  直到 7 年前
        1
  •  1
  •   Pieter van den Ham    7 年前

    PHP无法区分未赋值变量和空变量。 这使得跟踪哪些属性应该被覆盖变得非常不可避免。

    我知道你有两个顾虑:

    • 保持 Data 不变的
    • 保持 数据 干净(例如强制使用严格类型)

    能够跟踪“已定义”和“未定义”属性的最简单的数据结构之一是 \stdClass 对象(但数组也很好)。 通过移动 merge() 方法进入 数据 类可以隐藏任何实现细节-保持接口干净。

    实现可能如下所示:

    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"