我正在编写一个旨在扩展的“父”类。我希望扩展器在定义类时设置一些属性,但我不希望这些值在最初设置后发生变化。这样做的正确方法是什么?
更新
为了澄清,我希望该属性充当扩展类设置一次但不能在之后修改的常量。出于这个原因,我不能简单地使用getter方法将其设为私有属性。扩展类可以添加一个setter,对吗?
答案 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