在这种情况下,您可以使用访问者设计模式。在您的Product
类中实现该方法addToBill
并作为参数传递一个实现您的账单接口的实例,IBill
. IBill
支持addToTotal
将接受项目中所有可用信息的方法;在你的情况下,这是一个价格。例如:
interface IBill {
/* needs to be public because PHP doesn't understand the concept of
friendship
*/
function addToTotal($price);
}
class Bill implements IBill {
private $total = 0;
function addToTotal($price) {
$this->total += $price;
}
...
}
class ShoppingCart {
private $items = array();
function addItem($id, $product, $quantity) {
if (isset($this->items[$id])) {
$this->items[$id]->addQuantity($quantity);
} else {
$this->items[$id] = new LineItem($product, $quantity);
}
}
private createBill() {
$bill = new Bill;
foreach ($this->items AS $lineItem) {
$lineItem->addToBill($bill);
}
return ...;
}
}
class LineItem {
private $product, $quantity;
function __constructor($product, $quantity) {...}
function addToBill(IBill $bill) {
$this->product->addToBill($bill, $quantity);
}
function addQuantity($quantity) {
$this->quantity += $quantity;
}
...
}
class Product {
private $name, $description, $price;
function __constructor(...) {...}
function addToBill(IBill $bill, $quantity) {
$bill->addToTotal($this->price * $quantity);
}
...
}
然而,你总是会在摇摇欲坠的地方结束。上面需要一个类似 的方法addToTotal
,它引入了一个不变量(总数必须与行项目价格和数量的产品总和相匹配),这正是“告诉,不要问”应该避免的事情。您可以尝试不使用addToTotal
: * 取消Bill
; ShoppingCart
跟踪总数。将价格传递addItem
给产品和数量;addItem
更新总数。这在某种程度上违背了开设课程的目的,因为您没有使用LineItem
或Product
使用太多。这还增加了一个不变量,即传递的价格和创建产品时给出的价格应该匹配,但如果它们不匹配,它不应该引起问题(这会很奇怪)。*addItem
实例化Product
和LineItem
; addItem
更新总数。添加先前添加的附加项目时,必须有一个附加不变量,即传入的值$price
必须与先前调用中传递的数量相匹配,或者addItem
根本不允许添加附加的现有项目。* 将所有物品一起丢弃。ShoppingCart
存储产品 ID 和数量。每次调用都会addItem
更新总数。createBill
使用已经计算的总数。甚至比其他更多,这将不同的关注点结合在一起。
还有其他潜在的设计,但每个人都会遇到某种问题,通常与关注点分离、引入不变量和增加复杂性有关。总之,直接在计算总价的方法中访问一个行项目的总价不仅是最简单的,而且是最简洁的,并且产生错误的可能性最小。