【问题标题】:How to design php class with properties that can only be set once如何设计具有只能设置一次的属性的php类
【发布时间】:2017-02-02 19:50:53
【问题描述】:

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

更新:

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

【问题讨论】:

  • 请参阅Visibility,尤其是private,了解如何防止成员被其他人更改。
  • protected 设置为父类中的那些属性,子类在构造函数中获取这些属性,并且只为它们创建getter 方法。另一种方法是尝试工厂设计模式

标签: php oop inheritance abstract-class subclass


【解决方案1】:

您将不想写的变量设置为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只能通过Foo提供的Getter来访问,不能直接访问。这可以防止 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 防止任何子类修改方法。


关于你的 cmets:

如果 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 内部更改。并且在某种 setter 中不需要额外的逻辑。

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

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


附带说明:有一个RFC,旨在将不可变类和属性添加为本地语言功能。但是,截至今天,它仍处于草稿中。

【讨论】:

  • 但是是什么阻止了扩展类表单添加 setter 方法来稍后修改值?
  • @emerson 私有可见性阻止它
  • 如果Bar 添加了setMyProperty($value) 之类的方法会怎样? $this->myProperty 会因为它是私有的而无限期吗?
  • 另外,有没有办法在不通过构造函数传递变量的情况下做到这一点?我希望在继承的类中定义这些属性,以便它们受版本控制。
  • @emersonthis 我已更新答案以解决您的 cmets
【解决方案2】:

将属性声明为私有并通过受保护的设置器进行设置。在此设置器中,您可以进行控制

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 中有一个通用规则: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

【讨论】:

  • 很好的答案。我正在考虑对象存储方法。我绝对可以看到这在现实世界的项目中是如何真正有用的。我的用例更像是一个游戏/教学练习,我试图保持尽可能简单。有没有办法在不将值传递给构造函数的情况下执行私有变量方法?我希望这些值成为子类的一部分。
  • @emersonthis 您可以为您的组件类定义一个“配置”方法。和/或使用“MyClass”使用的硬编码属性。使用new MyComponent("Hello World");,或 $myComponent->configure(["property"=>"Hello World"]);
  • 常量 FOO 的覆盖值(在本例中为 2)
猜你喜欢
  • 1970-01-01
  • 2016-12-04
  • 1970-01-01
  • 1970-01-01
  • 2012-06-21
  • 2021-08-14
  • 2020-05-19
  • 2010-10-24
相关资源
最近更新 更多