如何设计php类的属性,只能设置一次


How to design php class with properties that can only be set once

我正在编写一个要扩展的"父"类。我希望扩展程序在定义类时设置一些属性,但我不希望这些值在最初设置后发生变化。正确的做法是什么?

更新:

澄清一下,我希望属性作为一个常量,扩展类设置一次,但不能修改以后。由于这个原因,我不能简单地让它成为带有getter方法的私有属性。扩展类可以只添加setter,对吧?

您将不想写入的变量设置为private,然后不为它们的子类提供任何mutator,例如

abstract class Foo 
{    
    private $value;    
    public function __construct($value) 
    {
        $this->value = $value;
    }
    protected function getValue() 
    {
        return $this->value;
    }
}

根据$value是什么,您可能需要在将其返回给子类之前将其clone

在您的孩子类中,您将执行

class Bar extends Foo 
{
    public function someMethodUsingValue()
    {
        $value = $this->getValue();
        // do something with $value
    }
}
$bar = new Bar(42);
$bar->someMethodUsingValue();

因为$value是私有的,Bar只能通过Foo提供的Getter访问它,而不能直接访问它。这可以防止Bar更改它(当然,只要它不是对象,请参阅上面关于克隆的说明)。

如果你的子类需要一个不同于父类的构造函数,你需要确保它实际上调用了父类的构造函数,并且是不可变的值,例如在Bar

public function __construct($value, $something)
{
    parent::__construct($value);
    $this->something = $something;
}

参见http://php.net/manual/en/language.oop5.visibility.php


另一个(也是更可取的)选项是将不可变值封装在一个或几个类中,并在父类中聚合/组合它们,而不是继承它们:

class Bar
{
    private $data;
    public function __construct()
    {
        $this->data = new ImmutableValue(42);
    }
    public function getValue()
    {
        return $this->data->getValue();
    }
}
class ImmutableValue
{
    private $value;
    public function __construct($value)
    {
        $this->value = $value;
    }
    public function getValue()
    {
        return $this->value;
    }
}
正如你所看到的,这个没有使用继承。不需要。有关详细信息,请参阅以下两篇文章:
  • https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose
  • http://martinfowler.com/bliki/ValueObject.html

既然你在问题中提到了常量,你显然可以在不使用继承或组合的情况下实现这一切,只需要设置一个真正的常量,例如

class Bar
{
    const VALUE = 42;
}
echo Bar::VALUE; // 42

或使用常量方法:

class Bar
{
    public final function getValue() 
    {
        return 42;
    }
}

将getter设置为final可以防止任何子类修改该方法。


关于你的评论:

如果Bar添加了像setMyProperty($value)这样的方法会发生什么?$this->myProperty是不确定的,因为它是私有的?

您最终会在Bar实例中设置一个具有相同名称的新公共属性。但是父类的私有实例变量将保持不变。

class Foo 
{
    private $value = 1;
}
class Bar extends Foo 
{
    public function setValue($value)
    {
        $this->value = $value;
    }
}
$bar = new Bar;
$bar->setValue(42);
var_dump($bar);

会给你

object(Bar)#1 (2) {
  ["value:private"]=>
  int(1)
  ["value"]=>
  int(42)
}

所以不,扩展类不能只添加setter。隐私就是隐私。


还有,有没有一种方法可以在不通过构造函数传递变量的情况下做到这一点?我希望这些属性定义在继承类[…]

您可以利用本页其他地方显示的模式,其中添加一个setter方法,该方法只允许设置一次,然后在后续调用时引发异常。然而,我不喜欢这样,因为这是构造函数的真正用途。我不认为通过构造函数传递变量有什么问题。想想看:

public function __construct($somethingElse)
{
    parent::__construct(42);
    $this->somethingElse = $somethingElse;
}

这样,$value是从继承类的构造函数设置的。没有办法从外部改变它,也没有办法从内部改变它。在setter中不需要额外的逻辑。

[…]所以它们是版本控制的。

我不确定你真的是说版本控制。如果你想要版本控制,使用一个合适的系统,例如git或mercurial或任何其他广泛使用的VCS。


旁注:有一个RFC,旨在将不可变类和属性添加为本机语言特性。然而,截至今天,它仍处于草案阶段。

声明该属性为私有,并通过protected setter设置它。在这个setter中,您可以设置控件

abstract class YourClass {
   private $myProperty;
   protected function setMyProperty($value) {
        if(is_null($this->myProperty)) {
             $this->myProperty = $value;
        }else {
             throw new Exception("You can set this property only once");
        }
   }
   protected function getMyProperty() {
          return $this->myProperty;
   }
}

为了获取属性,你还需要一个受保护的getter(或public,如果你需要从外部访问)。

另一种观点:

OOP中有一个通用规则:优先选择组合而不是继承

试着想想另一种方法:如果你需要一组不可变的属性,你可以把它们封装在一个对象中,然后把这个对象当作组件使用。

class MyClass {
    /**
     * @var MyComponent
     */
    protected $component;
    public function __construct($property) {
        $this->setComponent(new MyComponent($property));
    }
    protected function setComponent(MyComponent $myComponentInstance) {
        $this->component = $myComponentInstance;
    }
    public function doSomething() {
        echo $this->component->getMyProperty();
    }
}
class MyComponent {
    private $myProperty;
    public function __construct($property) {
        $this->myProperty = $property;
    }
    public function getMyProperty() {
        return $this->myProperty;
    }
}
$myClass = new MyClass("Hello World");
$myClass->doSomething();
编辑:

阅读你的编辑,我认为你想要这个:

abstract class MyAbstractClass {
    const FOO = 1;
    public function getFoo() {
        // hint: use "static" instead of "self"
        return static::FOO;
    }
}
class MyClass extends MyAbstractClass{
    // override
    const FOO = 2;
}
$myClass = new MyClass();
echo $myClass->getFoo(); // 2