2012-04-30 19 views
5

In seguito allo "Tell, don't Ask"-principle non si devono usare getter in OOP.OOP - senza GET/SET

Ma come risolvere i problemi in cui (almeno io la penso così) uno ha effettivamente bisogno di alcune "informazioni interne" da un oggetto? Quindi, come modificare il seguente esempio in modo che la funzione create_bill() non abbia bisogno di CHIEDERE il prezzo per ciascun articolo?

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; 
    } 
} 

Usage:

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

se un carrello della spesa contiene alist di voci, perché è male avere il carrello della spesa in grado di imparare il valore del prezzo di ogni articolo? – user12345613

+1

in base al principio "Tell do not ask", sembra essere malvagio. – Tom

+0

Dove hai sentito questo principio "non dire chiedere"? Non ne ho mai sentito. – xxpor

risposta

7

Se non si utilizza i dati richiesti/stato per modificare l'oggetto a cui ti riferisci, io non credo che ci sia qualcosa di sbagliato con l'utilizzo di getter a tutti .

Il principio parla di uno scenario come questo:

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

che dovrebbe essere trasformato in qualcosa di simile $item->set_minimum_price(5).

4

Ci sono due elementi dal articolo "Tell, Don't Ask" ti riferisci a quella orso attento esame:

  1. [...] non chiedere [oggetti] domande sul loro stato, prendere una decisione e poi dire loro cosa fare.
    Con il tuo esempio particolare, chopping_cart interroga solo gli oggetti & prende una decisione. Fondamentalmente, non continua a dire loro cosa fare.
  2. Secondo Design by Contract, purché i metodi (query e comandi) possano essere mescolati liberamente e non vi sia alcun modo per violare l'invariante della classe in tal modo, allora si è ok. Ma mentre si mantiene invariata la classe, si può anche aver aumentato drasticamente l'accoppiamento tra il chiamante e il chiamato in base a quanto stato esposto.
    Le chiamate Getter possono generalmente essere mescolate liberamente, quindi non c'è alcun accoppiamento causato dalla necessità di mantenere invarianti di classe.

Pertanto, i getter non causano i problemi che il principio "dice, non chiedere" dovrebbe impedire.

2

In questo caso è possibile utilizzare Visitor design pattern. Nella classe Product implementare il metodo addToBill e passare come argomento un'istanza che implementa l'interfaccia di fatturazione, IBill. IBill supporta un metodo addToTotal che accetta tutte le informazioni necessarie disponibili nell'elemento; nel tuo caso, questo è un prezzo Ad esempio:

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); 
    } 
    ... 
} 

Tuttavia, si finisce sempre su terreno instabile. Quanto sopra richiede un metodo come addToTotal, che introduce un invariante (il totale deve corrispondere alla somma dei prodotti dei prezzi e delle quantità dell'elemento pubblicitario), solo il tipo di cosa che "Tell, Do not Ask" dovrebbe evitare. Puoi provare a farlo senza addToTotal: * Elimina con Bill; ShoppingCart tiene traccia del totale. Passa il prezzo a addItem in aggiunta alla quantità di prodotto &; addItem aggiorna il totale. Questo in qualche modo sconfigge lo scopo di avere classi, dato che non stai usando LineItem o Product per molto.Ciò aggiunge anche all'invarianza che il prezzo passato e il prezzo dato al momento della creazione del prodotto dovrebbero coincidere, anche se non lo fanno, non dovrebbero causare problemi (sarebbe semplicemente strano). * Avere addItem un'istanza Product e LineItem; addItem aggiorna il totale. Quando si aggiungono ulteriori elementi che sono stati precedentemente aggiunti, ci deve essere sia un invariante aggiuntiva che il passato-in $price deve corrispondere al valore passato nei precedenti bandi, o addItem semplicemente non può essere permesso di aggiungere ulteriori, elementi esistenti. * Elimina gli articoli tutti insieme. ShoppingCart memorizza l'ID del prodotto e la quantità. Ogni chiamata a addItem aggiorna il totale. createBill utilizza il totale già calcolato. Anche più degli altri, questo unisce separate concerns.

Ci sono altri disegni potenziali, ma ognuno soffre di qualche tipo di problema, tipicamente relativo alla separazione degli argomenti, introducendo invarianti e aggiungere complessità. Nel complesso, l'accesso al prezzo totale di un elemento pubblicitario direttamente all'interno del metodo che calcola il totale non è solo il più semplice, ma più pulito e meno probabile che generi errori.