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;
}
}
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'];
}
}
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;
}
}
<?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();
7条答案
按热度按时间dphi5xsq1#
不可变对象没有setter方法。句号。
每个人都希望
setXyz()
方法有一个void返回类型(或者在松散类型的语言中什么都不返回),如果你真的在你的不可变对象中添加setter方法,它会让人们感到困惑,并导致丑陋的bug。68de4m5k2#
在我看来,对象对于值对象应该是不可变的。除此之外,它没有太多的好处,除非你在整个应用程序中共享你的对象。
这里有一些错误的答案,一个不可变的对象可以有setter。这里是PHP中不可变对象的一些实现。
示例1.
正如您所看到的,一旦示例化,就不能更改任何值。
示例2:对于
setters
:有几种可能的实现,这决不是一个排他性的列表。记住,在PHP中,反射总是有一种方法来解决这个问题,所以不变性最终都是你的想法!
将不可变对象作为final对象通常也是一种很好的做法。
编辑:
6ioyuze23#
不可变对象在初始创建后不能更改,因此使用setter方法没有意义,因为它违背了基本原则。
您可以通过操纵类成员的可见性和覆盖magic
__set()
方法来实现一些变通方法,以模拟PHP中的不可变性,但由于不可变性不是PHP语言的特性,因此无法保证其不可变性。我相信有人曾经写过一个扩展来提供一个不可变的PHP值类型,所以你可以谷歌一下。
ffscu2ro4#
在PHP中使对象不可变是非常容易的。下面是一个优雅而方便的方法。
您所需要做的就是使用特定的
__get()
和__set()
魔术方法创建基本抽象类,并在子对象中扩展此基类。如果您使用值对象(例如,对于DDD),这非常适用。
下面是基类:
现在是一个子对象(好吧,它的类)。
它会在尝试设置某个值时抛出异常。基本上它是不可变的。
这就是了。
创建尽可能多的不可变对象。通过构造函数创建新对象。释放它们,并在需要时重新创建新对象(如果需要,向基类或扩展类添加特定的创建者方法,静态或示例方法)。
然而,它会方便地将其所有属性公开为只读(用于某种序列化或类似操作),这与我们将其设为私有不同(但即使如此,我们也可以使用JsonSerializable接口,使序列化灵活到我们需要私有属性或更剧烈的转换)。
最后,我们不能错误地示例化
BaseValueObject
,因为它是一个抽象类。cfh9epnr5#
我做了一个避免使用Reflection的小特性来简化不变性的实现:https://github.com/jclaveau/php-immutable-trait
显然,由于它不是一个语言特性,所以它不会通过魔法来质疑变异,而是减轻了在应用之前必须克隆当前示例的变异器的代码。
希望它能帮助这里的一些人!
PS:关于这个小功能的建议真的很受欢迎:):https://github.com/jclaveau/php-immutable-trait/issues
dwbf0jvd6#
从一个不可变的对象中,你可以得到它的值,但是没有办法修改它们。下面你可以看到一个不可变类的例子:
gc0ot86w7#
如果你想在一个类和对象上使用setter,这是完全可以的,我们在需要设置对象数据时一直都是这样做的,只是不要称之为不可变的。
开发世界中的许多事情都是主观的--我们的方法、方法论等--但“不可变”是一个非常可靠的定义:
“不可变”:
如果你想要一个不可变的对象,这意味着它在示例化之后不能被改变。这对于一些事情是很好的,比如来自DB的数据需要在周期的持续时间内保持不变。
如果您需要在执行严修化之后呼叫对象并设定或变更其上的数据,这就不是不可变的对象。
你会把一辆汽车的两个轮子去掉,把它叫做摩托车吗?
有一些关于“不可变”类中的方法不使用“set”这个词的讨论,但是这并不能阻止它们作为一个设置数据的方法的功能。你可以将其命名为
thisDoesNotSetAnything(int $id)
,并允许数据被传入,从而改变对象。它将是一个setter,因此对象是可变的。