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

时间:2016-09-25 11:41:39

标签: php oop inheritance abstract-class subclass

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

更新

为了澄清,我希望该属性充当扩展类设置一次但不能在之后修改的常量。出于这个原因,我不能简单地使用getter方法将其设为私有属性。扩展类可以添加一个setter,对吗?

2 个答案:

答案 0 :(得分:4)

您将不想写的变量设置为private,然后不要为子类提供任何变更器,例如

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只能通过提供的Getter of Foo访问它,但不能直接访问它。这可以防止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;
    }
}

如您所见,这个不使用继承。不需要它。有关详细信息,请参阅以下两篇文章:

既然你在问题中提到了常量,那么显然你可以在不使用继承或组合的情况下实现整个事情,只需设置一个实常数,例如。

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实例中使用相同的名称设置新的公共属性。但是父级的私有实例var将保持不变。

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是从继承类中的构造函数设置的。没有办法从外面改变它,也不能从Bar内部改变它。并且在某种设定器中不需要额外的逻辑。

  

[...]所以他们受版本控制。

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

旁注:有一个RFC,旨在将Immutable类和属性添加为本机语言功能。但是,截至今天,它仍处于草案状态。

答案 1 :(得分:3)

将属性声明为私有,并通过受保护的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(如果您需要从外部访问,则需要公共)。

另一种观点:

OOP中有一个共同的规则:prefer composition over inheritance

尝试考虑另一种方法:如果需要一组不可变属性,可以将它们封装在一个对象中,然后将该对象用作组件。

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