创建无法在 PHP 中更改的对象是个好主意吗?
例如具有 setter 方法的日期对象,但它们将始终返回对象的新实例(具有修改的日期)。
这些对象是否会让其他使用该类的人感到困惑,因为在 PHP 中您通常希望对象发生变化?
例子
$obj = new Object(2);
$x = $obj->add(5); // 7
$y = $obj->add(2); // 4
创建无法在 PHP 中更改的对象是个好主意吗?
例如具有 setter 方法的日期对象,但它们将始终返回对象的新实例(具有修改的日期)。
这些对象是否会让其他使用该类的人感到困惑,因为在 PHP 中您通常希望对象发生变化?
例子
$obj = new Object(2);
$x = $obj->add(5); // 7
$y = $obj->add(2); // 4
不可变对象没有 setter 方法。时期。
每个人都会期望一个setXyz()
方法有一个 void 返回类型(或者在松散类型的语言中什么都不返回)。如果您确实将 setter 方法添加到您的不可变对象,它将使人们感到困惑并导致丑陋的错误。
在我看来,对象对于值对象应该是不可变的。除此之外,除非您在整个应用程序中共享您的对象,否则它并没有太多好处。
这里有一些错误的答案,一个不可变的对象可以有 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 中解决这个问题,所以最终不变性完全在你的脑海中!
将不可变对象作为最终对象通常也是一种好习惯。
编辑:
不可变对象在初始创建后无法更改,因此使用 setter 方法毫无意义,因为它违反了该基本原则。
您可以通过操纵类成员的可见性和覆盖魔术__set()
方法来实现一些变通方法来模拟 PHP 中的不变性,但它不能保证不可变,因为不变性不是 PHP 语言的特性。
我相信有人曾经写过一个扩展来在 PHP 中提供一个不可变的值类型,所以你可以用谷歌搜索。
我做了一个小特征,避免使用反射来简化不变性的实现: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;
}
}
希望它可以帮助这里的一些人!
PS:非常欢迎对这个小功能提出建议:):https ://github.com/jclaveau/php-immutable-trait/issues
从不可变对象中,您可以获取其值,但无法修改它们。在这里你可以看到一个不可变类的例子:
<?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();
如果你想要一个类和对象的设置器,这很好,我们一直这样做,因为我们需要设置对象数据。只是不要称它为不可变的。
开发世界中的许多事情都是主观的——我们的方法、方法论等——但“不可变”是一个非常可靠的定义:
“不可变”:
- 随着时间的推移不变或无法更改。
如果您想要一个不可变对象,则意味着它在实例化后无法更改。这对于需要在循环期间保持不变的数据库等数据非常有用。
如果您需要在实例化后调用对象并在其上设置或更改数据,这不是不可变对象。
您会从汽车上取下两个轮子并称其为摩托车吗?
有一些关于“不可变”类的方法被命名为没有“set”一词的讨论,但这并不能阻止它们作为设置数据的方法的功能。您可以调用它thisDoesNotSetAnything(int $id)
并允许传递更改对象的数据。它将是一个 setter,因此该对象是可变的。
使对象不可变非常容易。这是一种优雅而方便的方法。
您需要做的就是创建具有特定__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
,因为它是一个抽象类。从各个角度来看都是不错的优雅解决方案。