dongpinken0498 2016-09-25 11:41
浏览 92

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

I'm writing a "parent" class that is meant to be extended. I want the extenders to set a handful of properties once when they define the class but I don't want those values to ever change after they've been initially set. What's the correct way to do this?

Update:

To clarify, I want the property to act as a constant that the extending class sets once but can't modify afterwards. For this reason I can't simply make it a private property with a getter method. The extending class could just add a setter, right?

  • 写回答

2条回答 默认 最新

  • doufu2496 2016-09-25 11:47
    关注

    You set the variables you dont want to be writable as private and then don't provide any mutators for them to the child classes, e.g.

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

    Depending on what $value is, you might need to clone it before returning it to the child class.

    In your child class, you'd do

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

    Because $value is private, Bar can only access it through the provided Getter of Foo, but cannot access it directly. This prevents Bar from changing it (as long as it's not an object of course, see note above about cloning).

    If your child class needs a different constructor than the parent, you need to make sure it actually invokes the parent constructor with the to be immutable values, e.g. in Bar

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

    Also see http://php.net/manual/en/language.oop5.visibility.php


    Yet another (and preferable) option would be to encapsulate the immutable value(s) in one or several classes and aggregate/compose them within the parent classes, instead of inheriting them:

    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;
        }
    }
    

    As you can see, this one does not use inheritance. It's not needed. Refer to the following two articles for more details:


    Since you mention constants in your question, you can obviously achieve the whole thing without using inheritance or composition and just set a real constant, e.g.

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

    or use a constant method:

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

    Making the getter final prevents any subclass from modifying the method.


    Regarding your comments:

    What would happen if Bar added a method like setMyProperty($value)? Will $this->myProperty be indefinite because it's private?

    You would end up with a new public property set in the Bar instance with the same name. But the private instance var of the parent would be left unchanged.

    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);
    

    Would give you

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

    So no, the extending class could not just add a setter. Private is private.


    Also, is there a way to do this without passing the variables through the constructor? I'd like those attributes to be defined in the inherited class […]

    You could utilize the pattern shown elsewhere on this page, where you add a setter method that allows setting only once and then raises an exception on subsequent calls. However, I don't like that since it's really what a constructor is for. And I don't see the issue with passing the variables through the constructor. Consider this:

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

    This way, $value is set from the constructor in the inherited class. There is no way to change it from the outside, nor from within Bar. And no additional logic required in some sort of setter.

    […] so they're version controlled.

    I am not sure you really mean version controlled. If you want version control, utilize a proper system for that, e.g. git or mercurial or any other widely available VCS.


    On a side note: there is an RFC, that aims to add Immutable classes and properties as a native language feature. However, it's still in Draft as of today.

    评论

报告相同问题?

悬赏问题

  • ¥15 #MATLAB仿真#车辆换道路径规划
  • ¥15 java 操作 elasticsearch 8.1 实现 索引的重建
  • ¥15 数据可视化Python
  • ¥15 要给毕业设计添加扫码登录的功能!!有偿
  • ¥15 kafka 分区副本增加会导致消息丢失或者不可用吗?
  • ¥15 微信公众号自制会员卡没有收款渠道啊
  • ¥100 Jenkins自动化部署—悬赏100元
  • ¥15 关于#python#的问题:求帮写python代码
  • ¥20 MATLAB画图图形出现上下震荡的线条
  • ¥15 关于#windows#的问题:怎么用WIN 11系统的电脑 克隆WIN NT3.51-4.0系统的硬盘