PHP中的不可变对象?

时间:2013-01-20 17:16:22

标签: php class immutability

创建无法在PHP中更改的对象是一个好主意吗?

例如,具有setter方法的日期对象,但它们将始终返回对象的新实例(具有修改日期)。

这些对象是否会让使用该类的其他人感到困惑,因为在PHP中,您通常希望该对象发生变化?

实施例

$obj = new Object(2);

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

8 个答案:

答案 0 :(得分:22)

不可变对象没有setter方法。周期。

每个人都希望setXyz()方法具有void返回类型(或者在松散类型的语言中不返回任何内容)。如果你确实为你的不可变对象添加了setter方法,那么它会让人感到困惑并导致丑陋的错误。

答案 1 :(得分:9)

在我看来,对象对于值对象应该是不可变的。除此之外,除非您在整个应用程序中共享对象,否则它没有太大的好处。

这里有一些错误的答案,不可变对象可以有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;
    }
}

可以实现多种实现,这绝不是排他性列表。请记住,使用Reflection总有一种方法可以在PHP中解决这个问题,所以最终你的不变性就在你的脑海中!

将不可变对象作为最终对象通常也是一种好习惯。

编辑:

  • 更改setX for withX
  • 添加了关于最终的评论

答案 2 :(得分:6)

不可变对象在初始创建后不能更改,因此使用setter方法没有意义,因为它违背了基本原则。

您可以通过操纵类成员可见性并覆盖magic __set()方法来实现一些变通方法来模拟PHP中的不变性,但不能保证不可变,因为不变性不是该语言的一个特性。我相信有人曾经写过一个扩展来在PHP中提供一个不可变的值类型,所以你可以谷歌那样。

答案 3 :(得分:0)

我做了一些避免使用反射来简化不变性实现的特征:https://github.com/jclaveau/php-immutable-trait

很明显,由于它不是语言功能,因此不会通过魔术弹mutation突变,但是可以减轻必须先克隆当前实例才能应用的突变体的代码。将其应用于Massimiliano的示例中

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,而是在Kanstantsin K注意到的情况下返回了$ this。
  • 在本机PHP https://secure.php.net/manual/en/class.datetimeimmutable.php中,具有更改器,这些更改器将返回应用了修改的新实例。因此,复制粘贴语句说不可变对象不应该包含变异符似乎并不有趣。
  • 使用“ withXXX”代替“ setXXX”的做法非常有趣,感谢您的建议!我个人使用“ becomesXXX”来链接实例的可变性(特征SwitchableMutability中的可选API)的api。

希望它可以帮助这里的某些人!

PS:真的欢迎您提出有关此小功能的建议:):https://github.com/jclaveau/php-immutable-trait/issues

答案 4 :(得分:0)

从一个不可变的对象,您可以获取其值,但无法修改它们。在这里,您可以看到一个不可变类的示例:

<?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(); 

答案 5 :(得分:0)

如果您想在类和对象上使用setter,这是完全可以的,我们会一直设置对象数据,因此我们会一直这样做。只是根本不称它为一成不变。

开发人员世界中的许多事情都是主观的-我们的方法,方法论等-但“不变”是一个非常扎实的定义:

  

“不可变”:
  -随时间不变或无法更改。

如果您想要一个不可变的对象,则意味着在实例化之后不能对其进行更改。这对于诸如DB数据库中需要在整个周期内保持不变的数据之类的事情有好处。

如果您需要在实例化后调用该对象并设置或更改其上的数据,则这不是一个不变的对象。

您会从汽车上拆下两个车轮并将其称为摩托车吗?

关于“不可变”类的方法有一些讨论,这些方法不带单词“ set”,但是这并不能阻止它们作为设置数据的方法的功能。您可以将其称为thisDoesNotSetAnything(int $id),并允许在其中更改对象的数据进行传递。这将是一个setter,因此该对象是可变的。

答案 6 :(得分:0)

使对象不可变非常容易。这是一种优雅而方便的方法。

您需要做的就是使用特定的 __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'];
    }
}

就是这样。

它会在尝试设置某个值时引发异常。基本上它是不可变的。

然而,它会方便地将其所有属性公开为只读(对于某种序列化等),这与我们将它们设为私有不同。

最后不能错误地实例化BaseValueObject,因为它是一个抽象类。从各个角度来看,都是不错的优雅解决方案。

答案 7 :(得分:0)

希望不变性功能快点推出https://wiki.php.net/rfc/immutability