PHP中的不可变对象?

9q78igpj  于 2022-11-21  发布在  PHP
关注(0)|答案(7)|浏览(297)

在PHP中创建不能更改的对象是个好主意吗?
例如,一个日期对象,它有setter方法,但它们总是返回该对象的新示例(带有修改的日期)。
这些对象是否会让其他使用该类的人感到困惑,因为在PHP中,您通常希望对象发生变化?
范例

$obj = new Object(2);

$x = $obj->add(5); // 7
$y = $obj->add(2); // 4
dphi5xsq

dphi5xsq1#

不可变对象没有setter方法。句号。
每个人都希望setXyz()方法有一个void返回类型(或者在松散类型的语言中什么都不返回),如果你真的在你的不可变对象中添加setter方法,它会让人们感到困惑,并导致丑陋的bug。

68de4m5k

68de4m5k2#

在我看来,对象对于值对象应该是不可变的。除此之外,它没有太多的好处,除非你在整个应用程序中共享你的对象。
这里有一些错误的答案,一个不可变的对象可以有setter。这里是PHP中不可变对象的一些实现。
示例1.

class ImmutableValueObject
{
    private $val1;
    private $val2;

    public function __construct($val1, $val2)
    {
        $this->val1 = $val1;
        $this->val2 = $val2;
    }

    public function getVal1()
    {
        return $this->val1;
    }

    public function getVal2()
    {
        return $this->val2;
    }
}

正如您所看到的,一旦示例化,就不能更改任何值。
示例2:对于setters

class ImmutableValueObject
{
    private $val1;
    private $val2;

    public function __construct($val1, $val2)
    {
        $this->val1 = $val1;
        $this->val2 = $val2;
    }

    public function getVal1()
    {
        return $this->val1;
    }

    public function withVal1($val1)
    {
        $copy = clone $this;
        $copy->val1 = $val1;

        return $copy;    // here's the trick: you return a new instance!
    }

    public function getVal2()
    {
        return $this->val2;
    }

    public function withVal2($val2)
    {
        $copy = clone $this;
        $copy->val2 = $val2;

        return $copy;
    }
}

有几种可能的实现,这决不是一个排他性的列表。记住,在PHP中,反射总是有一种方法来解决这个问题,所以不变性最终都是你的想法!
将不可变对象作为final对象通常也是一种很好的做法。
编辑:

  • 已将setX更改为withX
  • 添加了关于final的备注
6ioyuze2

6ioyuze23#

不可变对象在初始创建后不能更改,因此使用setter方法没有意义,因为它违背了基本原则。
您可以通过操纵类成员的可见性和覆盖magic __set()方法来实现一些变通方法,以模拟PHP中的不可变性,但由于不可变性不是PHP语言的特性,因此无法保证其不可变性。
我相信有人曾经写过一个扩展来提供一个不可变的PHP值类型,所以你可以谷歌一下。

ffscu2ro

ffscu2ro4#

在PHP中使对象不可变是非常容易的。下面是一个优雅而方便的方法。
您所需要做的就是使用特定的__get()__set()魔术方法创建基本抽象类,并在子对象中扩展此基类。
如果您使用值对象(例如,对于DDD),这非常适用。
下面是基类:

abstract class BaseValueObject
{
    public function __get(string $propertyName)
    {
        return $this->$propertyName;
    }

    public function __set(string $propertyName, $value): void
    {
        throw new \Exception("Cannot set property {$propertyName}. The object is immutable.");
    }
}

现在是一个子对象(好吧,它的类)。

class CategoryVO extends BaseValueObject
{

    public $id;
    public $name;

    public function __construct(array $data)
    {
        $this->id = $data['id'];
        $this->name = $data['name'];
    }
}

它会在尝试设置某个值时抛出异常。基本上它是不可变的。
这就是了。
创建尽可能多的不可变对象。通过构造函数创建新对象。释放它们,并在需要时重新创建新对象(如果需要,向基类或扩展类添加特定的创建者方法,静态或示例方法)。
然而,它会方便地将其所有属性公开为只读(用于某种序列化或类似操作),这与我们将其设为私有不同(但即使如此,我们也可以使用JsonSerializable接口,使序列化灵活到我们需要私有属性或更剧烈的转换)。
最后,我们不能错误地示例化BaseValueObject,因为它是一个抽象类。

cfh9epnr

cfh9epnr5#

我做了一个避免使用Reflection的小特性来简化不变性的实现:https://github.com/jclaveau/php-immutable-trait
显然,由于它不是一个语言特性,所以它不会通过魔法来质疑变异,而是减轻了在应用之前必须克隆当前示例的变异器的代码。

class ImmutableValueObject
{
    use JClaveau\Traits\Immutable;

    private $val1;
    private $val2;

    public function __construct($val1, $val2)
    {
        $this->val1 = $val1;
        $this->val2 = $val2;
    }

    public function getVal1()
    {
        return $this->val1;
    }

    public function withVal1($val1)
    {
        // Just add these lines at the really beginning of methods supporting
        // immutability ("setters" mostly)
        if ($this->callOnCloneIfImmutable($result))
            return $result;

        // Write your method's body as if you weren't in an Immutable class
        $this->val1 = $val1;
        return $this;
    }

    public function getVal2()
    {
        return $this->val2;
    }

    public function withVal2($val2)
    {
        if ($this->callOnCloneIfImmutable($result))
            return $result;

        $this->val2 = $val2;

        return $this;
    }
}
  • 你可以看到你没有在这里返回$copy,而是返回$this,正如KansantinK所注意到的。
  • 在原生的PHP中,https://secure.php.net/manual/en/class.datetimeimmutable.php有mutator,它会返回经过修改的新示例。所以复制粘贴语句说不可变对象不应该有mutator看起来并不是很有趣。
  • 使用“withXXX”而不是“setXXX”的做法非常有趣,谢谢你的建议!我个人使用“becomesXXX”作为链接示例可变性的API(特性SwitchableMutability中的可选API)。

希望它能帮助这里的一些人!
PS:关于这个小功能的建议真的很受欢迎:):https://github.com/jclaveau/php-immutable-trait/issues

dwbf0jvd

dwbf0jvd6#

从一个不可变的对象中,你可以得到它的值,但是没有办法修改它们。下面你可以看到一个不可变类的例子:

<?php 

declare(strict_types=1);

final class Immutable 
{
    /** @var string */
    private $value;

    public static function withValue(string $value): self
    {
        return new self($value);
    }

    public function __construct(string $value) 
    {
        $this->value = $value;
    }

    public function value(): string 
    { 
        return $this->value;
    }
}

// Example of usage:
$immutable = Immutable::withValue("my value");
$immutable->value();
gc0ot86w

gc0ot86w7#

如果你想在一个类和对象上使用setter,这是完全可以的,我们在需要设置对象数据时一直都是这样做的,只是不要称之为不可变的。
开发世界中的许多事情都是主观的--我们的方法、方法论等--但“不可变”是一个非常可靠的定义:
“不可变”:

  • 不随时间改变的或不能改变的。
    如果你想要一个不可变的对象,这意味着它在示例化之后不能被改变。这对于一些事情是很好的,比如来自DB的数据需要在周期的持续时间内保持不变。
    如果您需要在执行严修化之后呼叫对象并设定或变更其上的数据,这就不是不可变的对象。
    你会把一辆汽车的两个轮子去掉,把它叫做摩托车吗?
    有一些关于“不可变”类中的方法不使用“set”这个词的讨论,但是这并不能阻止它们作为一个设置数据的方法的功能。你可以将其命名为thisDoesNotSetAnything(int $id),并允许数据被传入,从而改变对象。它将是一个setter,因此对象是可变的。

相关问题