2012-04-30 102 views
5

"Tell, don't Ask"-principle之后,不应在OOP中使用getter。OOP - 无GET/SET

但是如何解决问题(至少我认为是这样)实际上需要一些来自对象的“内部信息”?那么如何改变下面的例子,以便create_bill()函数不需要为每个项目的价格提供ASK?

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(); 
+0

如果购物车中包含项目列表,为什么购物车能够了解每个项目的价格的价值? – user12345613

+1

遵循所述的“告诉别问”原则,它似乎是邪恶的。 – Tom

+0

你从哪里听到这个“告诉别人问题”的原则?我从来没听说过。 – xxpor

回答

7

如果你不使用所请求的数据/状态来改变你指的是,我不认为对象有什么错误使用干将在所有。

有关方案的原则,会谈是这样的:

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

这应该成为像$item->set_minimum_price(5)

4

有从“Tell, Don't Ask”文章两项你指的是熊近距离观察:

  1. [...]不问[对象]关于他们的状态问题,做出决定,然后告诉他们该做什么。
    以您的具体示例为例,chopping_cart仅查询对象&做出决定。至关重要的是,它并没有继续告诉他们该做什么。
  2. 根据契约设计,只要你的方法(查询和命令)可以自由混合,并且没有办法通过这样做来违反类不变量,那么你就没问题。但是,当你维持类不变时,你可能已经戏剧性地增加了调用者和被调用者之间的耦合,这取决于你暴露了多少状态。
    Getter调用通常可以自由混合,因此不需要维护类不变量。

因此,获得者不会导致“告诉,不要求”原则应该防止的问题。

2

在这种情况下,您可以使用Visitor design pattern。在您的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这样的方法,其中引入了一个不变量(总数必须与行项目价格和数量的乘积之和相匹配),就像“Tell,Do not Ask”应该避免的那样。您可以尝试不使用addToTotal: *废除Bill; ShoppingCart跟踪总数。除产品&数量外,还将价格传递给addItem; addItem更新总数。这有点挫败了上课的目的,因为你没有使用LineItemProduct。这也增加了一个不变的情况,即传递的价格和产品创建时的价格应该匹配,但如果它们不这样做,则不应该引起问题(这很奇怪)。 *请addItem实例化ProductLineItem; addItem更新总数。添加先前添加的其他项目时,必须有一个额外的不变量,即传入的$price必须与先前调用中传递的数量相匹配,否则不能允许addItem添加其他现有项目。 *一起丢掉物品。 ShoppingCart存储产品ID和数量。每次致电addItem更新总数。 createBill使用已经计算的总数。比其他人更多,这个联合组织separate concerns

还有其他潜在的设计,但每个人都会遇到某种问题,通常涉及分离问题,引入不变量和增加复杂性。总而言之,在计算总计的方法中直接访问订单项的总价不仅是最简单,最干净,最不可能产生错误的方法。