18

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

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

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

例子

$obj = new Object(2);

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

7 回答 7

29

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

每个人都会期望一个setXyz()方法有一个 void 返回类型(或者在松散类型的语言中什么都不返回)。如果您确实将 setter 方法添加到您的不可变对象,它将使人们感到困惑并导致丑陋的错误。

于 2013-01-20T17:19:51.993 回答
16

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

这里有一些错误的答案,一个不可变的对象可以有 setters。这是 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 中解决这个问题,所以最终不变性完全在你的脑海中!

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

编辑:

  • 将 setX 更改为 withX
  • 添加了关于最终的评论
于 2015-11-25T02:52:00.243 回答
5

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

您可以通过操纵类成员的可见性和覆盖魔术__set()方法来实现一些变通方法来模拟 PHP 中的不变性,但它不能保证不可变,因为不变性不是 PHP 语言的特性。

我相信有人曾经写过一个扩展来在 PHP 中提供一个不可变的值类型,所以你可以用谷歌搜索。

于 2013-01-20T18:02:35.327 回答
0

我做了一个小特征,避免使用反射来简化不变性的实现: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,而是在 Kanstantsin K 注意到的情况下返回 $this。
  • 在原生 PHP中https://secure.php.net/manual/en/class.datetimeimmutable.php具有将返回新实例并应用修改的变异器。因此,复制粘贴语句说不可变对象不应该有 mutators 似乎不是很有趣。
  • 用“withXXX”代替“setXXX”的做法超级有趣,谢谢建议!我个人将“becomesXXX”用于链接实例可变性的 api(特征 SwitchableMutability 中的可选 API)。

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

PS:非常欢迎对这个小功能提出建议:):https ://github.com/jclaveau/php-immutable-trait/issues

于 2018-11-05T11:05:28.823 回答
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(); 
于 2019-08-16T12:50:58.793 回答
0

如果你想要一个类和对象的设置器,这很好,我们一直这样做,因为我们需要设置对象数据。只是不要称它为不可变的。

开发世界中的许多事情都是主观的——我们的方法、方法论等——但“不可变”是一个非常可靠的定义:

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

如果您想要一个不可变对象,则意味着它在实例化后无法更改。这对于需要在循环期间保持不变的数据库等数据非常有用。

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

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

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

于 2019-09-26T21:25:19.013 回答
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,因为它是一个抽象类。从各个角度来看都是不错的优雅解决方案。

于 2021-01-27T16:39:18.723 回答