PHP-允许包含对象访问容器对象信息


PHP - enabling contained object access to container object information

我有一个复合对象类,它有一个包含的组件类,在实例化时,我希望包含的对象能够实时访问复合对象的方法和属性。

在下面我写的"汽车"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对象的构造函数。此外,我将删除类CarsetHeight()方法。当你改变车轮类型或添加配件时,汽车的高度会发生变化。所有这些动作都应该有自己的方法。我还将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;

这样一切都清楚了。车轮高度不变,汽车高度也不变。但是你可以在汽车上安装不同尺寸的车轮,这样汽车的总高度就会发生变化。

  1. 在您的示例中,不需要通过引用Wheel的构造函数来传递$car。你想通过引用来达到什么目的?

  2. 你的代码不会破坏PHP:)

  3. 你可能想问问自己,Wheel是否需要对它所属的Car有如此深入的了解

例如:它真的需要访问其他(未来?)Car属性吗?比如它的maxSpeed?它的fuelConsumptionRate?它的price

它是否需要访问其startEngine()parkHere()功能?

此外,在未来,你有可能想把Wheel放在其他东西上,比如SupermarketShoppingCartRollerblades吗?

考虑根本不让Wheel引用Car。相反,您可以做的一件事是定义一个接口,比如IWheelOwner,它将声明Wheel可能期望其所有者提供的属性和功能。然后让Car实现该接口。然后,您仍然可以将$car传递到$wheel,但将其视为一个接口:

/** @var IWheelOwner */
private $wheelOwner;
final public function __construct(IWheelOwner $wheelOwner) {
    $this->wheelOwner = $wheelOwner;
}
  1. 你可以考虑换一种方式。您可以让CarWheel自身高度发生变化时调整其大小。如果您认为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;