5

遵循“告诉,不问”原则,在 OOP 中不应使用 getter。

但是如何解决(至少我是这么认为的)一个人实际上需要来自一个对象的一些“内部信息”的问题呢?那么如何更改下面的示例,使 create_bill() 函数不需要询问每件商品的价格?

class chopping_cart {

    private $itemlist = array();

    function item_add( $name, $price ) {
        $his->itemlist[]=new item( $name, $price );
    }
    private create_bill() {

        foreach $this->itemlist AS $element;
        $sum += $element->get_price();

    }
}


class item {
    private $name;
    private $price;
    function __construcor($name,$price) {...}
    function get_price() {
        return $price;
    }
}

用法:

$sc = new shopping_cart()
$sc->item_add( "Bike", 1.00 );
$sc->item_add( "Ship", 2.00 );
$sc->create_bill();
4

3 回答 3

7

如果您没有使用请求的数据/状态来更改您所指的对象,我认为使用 getter 根本没有任何问题。

原理讲述了这样一个场景:

if ($item->get_price() < 5) {
    $item->set_price(5);
}

那应该变成类似的东西$item->set_minimum_price(5)

于 2012-04-30T22:31:06.377 回答
4

您所指的“告诉,不要问”文章中有两个项目需要仔细检查:

  1. [...] 不要向 [对象] 询问他们的状态,做出决定,然后告诉他们该怎么做。
    对于您的特定示例,chopping_cart仅查询对象并做出决定。至关重要的是,它并没有继续告诉他们该做什么。
  2. 根据契约式设计,只要您的方法(查询和命令)可以自由混合,并且这样做不会违反类不变量,那么您就可以了。但是,在您保持类不变的同时,您可能还极大地增加了调用者和被调用者之间的耦合,具体取决于您暴露了多少状态。
    Getter 调用通常可以自由混合,因此不会因需要维护类不变量而导致耦合。

因此,吸气剂不会引起“告诉,不要问”原则应该防止的问题。

于 2012-04-30T22:49:58.217 回答
2

在这种情况下,您可以使用访问者设计模式。在您的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更新总数。这在某种程度上违背了开设课程的目的,因为您没有使用LineItemProduct使用太多。这还增加了一个不变量,即传递的价格和创建产品时给出的价格应该匹配,但如果它们不匹配,它不应该引起问题(这会很奇怪)。*addItem实例化ProductLineItem; addItem更新总数。添加先前添加的附加项目时,必须有一个附加不变量,即传入的值$price必须与先前调用中传递的数量相匹配,或者addItem根本不允许添加附加的现有项目。* 将所有物品一起丢弃。ShoppingCart存储产品 ID 和数量。每次调用都会addItem更新总数。createBill使用已经计算的总数。甚至比其他更多,这将不同的关注点结合在一起。

还有其他潜在的设计,但每个人都会遇到某种问题,通常与关注点分离、引入不变量和增加复杂性有关。总之,直接在计算总价的方法中访问一个行项目的总价不仅是最简单的,而且是最简洁的,并且产生错误的可能性最小。

于 2012-04-30T22:51:54.977 回答