我有一个复合对象类,它有一个包含的组件类,在实例化时,我希望包含的对象能够实时访问复合对象的方法和属性。
在下面我写的"汽车"answers"车轮"的例子中,建立了车轮直径和汽车高度之间的动态关系。改变汽车的高度会自动适当地改变车轮的直径,从而保持汽车的比例:
class Car {
private $wheel;
private $heightM;
function __construct() {
$this->wheel = new Wheel($this);
}
final public function wheel() {
return $this->wheel;
}
final public function setHeight($metres) {
$this->heightM = $metres;
}
final public function height() {
return $this->heightM;
}
}
class Wheel {
private $car;
final public function __construct(Car &$car) { // by reference
$this->car = &$car; // pointer
}
final public function diameter() {
return $this->car->height() * 20 / 100;
}
}
$toyota = new Car();
$toyota->setHeight(1);
print "Car height = " . $toyota->height() . "m"
. "; Wheel diameter = " . $toyota->wheel()->diameter() . "m"
. PHP_EOL;
$toyota->setHeight(2);
print "Car height = " . $toyota->height() . "m"
. "; Wheel diameter = " . $toyota->wheel()->diameter() . "m"
. PHP_EOL;
输出:
Car height = 1m; Wheel diameter = 0.2m
Car height = 2m; Wheel diameter = 0.4m
这正是我想要的结果,但我正在寻求指导,以确定以上内容是否构成了一种可接受的PHP编码方法,或者是否有更好的OOP/PHP方法可以实现这一点。谢谢
EDIT:为了让这个问题更具体:我真的在寻找这样的答案:否-这种方法会因为A、B和C的原因而破坏PHP;或否-请参阅正式设计模式X,这是实现此功能的公认标准。或者,是-使用指针来实现您在这里尝试做的事情是完全可以接受的(当然,这在我理解指针可能存在的风险的限制范围内)
从技术上讲,Car
类不是一个容器,而是一个复合对象。
容器是零个或多个相同类型(或抽象类型)的对象的集合,但所包含的对象可能存在,并且即使没有添加到容器中,也可以正常工作。让包含的对象知道容器的存在会使它们在没有容器的情况下很难(甚至不可能)使用。这显然是一种糟糕的做法。
复合对象是由较小的对象(类型较简单)组成的,不一定所有对象都是同一类型,而且它们的数量通常是预先知道的。组件归复合对象所有;当复合对象被破坏时,它们被破坏。让组件了解其所有者(复合对象)有时很有用,但大多数情况下并不需要。
您应该以自上而下的方式思考Car
及其组件的所有操作。它的所有方法都应该直接查询或修改它的基元属性($heightM
),如果需要,可以调用它的组件($wheel
)的方法来查询或修改它们。
从外部只能看到Car
对象。将其组件公开用于直接访问是一种糟糕的设计(Car::wheel()
就是这样做的)。
class Car {
private $wheel;
private $heightM;
function __construct($metres) {
$this->wheel = new Wheel(0);
$this->setHeight($metres);
}
final public function setHeight($metres) {
$this->heightM = $metres;
$this->wheel->setDiameter(0.2 * $metres);
}
final public function getHeight() {
return $this->heightM;
}
public function getWheelDiameter() {
return $this->wheel->getDiameter();
}
}
class Wheel {
private $diameter;
final public function __construct($diameter) {
$this->setDiameter($diameter);
}
final public function getDiameter() {
return $this->diameter;
}
public function setDiameter($diameter) {
$this->diameter = $diameter;
}
}
$toyota = new Car(1);
print "Car height = " . $toyota->getHeight() . "m"
. "; Wheel diameter = " . $toyota->getWheelDiameter() . "m"
. PHP_EOL;
$toyota->setHeight(2);
print "Car height = " . $toyota->getHeight() . "m"
. "; Wheel diameter = " . $toyota->getWheelDiameter() . "m"
. PHP_EOL;
我试着对你的代码做最小的修改,向你展示我将如何做到这一点。
改进
根据您计划如何使用这些类,最好将Wheel
类设为只读(放弃其setDiameter()
方法),并将Wheel
对象传递给Car
对象的构造函数。此外,我将删除类Car
的setHeight()
方法。当你改变车轮类型或添加配件时,汽车的高度会发生变化。所有这些动作都应该有自己的方法。我还将Car::getHeight()
的实现更改为也使用轮子大小。
class Car {
private $height;
private $wheel;
public function __construct($height, Wheel $wheel) {
$this->height = $height;
$this->wheel = $wheel;
}
public function changeWheelType(Wheel $wheel) {
$this->wheel = $wheel;
}
public function getHeight() {
// Implement the appropriate formula here
return $this->height + $this->getWheelDiameter() / 2;
}
public function getWheelDiameter() {
return $this->wheel->getDiameter();
}
}
class Wheel {
private $diameter;
final public function __construct($diameter) {
$this->diameter = $diameter;
}
final public function getDiameter() {
return $this->diameter;
}
}
$toyota = new Car(1, new Wheel(0.2));
print "Car height = " . $toyota->getHeight() . "m"
. "; Wheel diameter = " . $toyota->getWheelDiameter() . "m"
. PHP_EOL;
$toyota->changeWheelType(new Wheel(0.3));
print "Car height = " . $toyota->getHeight() . "m"
. "; Wheel diameter = " . $toyota->getWheelDiameter() . "m"
. PHP_EOL;
这样一切都清楚了。车轮高度不变,汽车高度也不变。但是你可以在汽车上安装不同尺寸的车轮,这样汽车的总高度就会发生变化。
在您的示例中,不需要通过引用
Wheel
的构造函数来传递$car
。你想通过引用来达到什么目的?你的代码不会破坏PHP:)
你可能想问问自己,
Wheel
是否需要对它所属的Car
有如此深入的了解
例如:它真的需要访问其他(未来?)Car
属性吗?比如它的maxSpeed
?它的fuelConsumptionRate
?它的price
?
它是否需要访问其startEngine()
和parkHere()
功能?
此外,在未来,你有可能想把Wheel
放在其他东西上,比如SupermarketShoppingCart
或Rollerblades
吗?
考虑根本不让Wheel
引用Car
。相反,您可以做的一件事是定义一个接口,比如IWheelOwner
,它将声明Wheel
可能期望其所有者提供的属性和功能。然后让Car
实现该接口。然后,您仍然可以将$car
传递到$wheel
,但将其视为一个接口:
/** @var IWheelOwner */
private $wheelOwner;
final public function __construct(IWheelOwner $wheelOwner) {
$this->wheelOwner = $wheelOwner;
}
- 你可以考虑换一种方式。您可以让
Car
在Wheel
自身高度发生变化时调整其大小。如果您认为20%的比率是Car
应该不知道的"Wheel
知识",只需给Wheel
一个readjustDiameter($ownerHeight)
函数,并在Car
的高度发生变化时调用它
他们的做法是让轮子成为一个具有静态函数的类。Php不支持多重继承,如果指针引用不明显(试着在两周后查看代码),它可能会变得繁忙。
class Car {
private $heightM;
final public function setHeight($metres) {
$this->heightM = $metres;
}
final public function height() {
return $this->heightM;
}
final public function wheel() {
return Wheel::getWheel($this);
}
}
class Wheel {
public static function getWheel($car) {
$wheel = [];
$wheel["diameter"] = $car->height() * 20 / 100;
return (object)$wheel;
}
}
$toyota = new Car();
$toyota->setHeight(1);
print "Wheel diameter = " . $toyota->wheel()->diameter . "m" . PHP_EOL;
$toyota->setHeight(2);
print "Wheel diameter = " . $toyota->wheel()->diameter . "m" . PHP_EOL;