2012-04-30 24 views
5

Po "Tell, don't Ask"-principle nie należy używać getter w OOP.OOP - bez GET/SET

Ale jak rozwiązywać problemy tam, gdzie (przynajmniej tak mi się wydaje) rzeczywiście potrzebujemy "informacji wewnętrznych" z obiektu? A więc jak zmienić poniższy przykład, aby funkcja create_bill() nie wymagała ZAPYTAĆ o cenę dla każdego elementu?

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

Zastosowanie:

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

jeśli koszyk zawiera alistę przedmiotów, dlaczego zakup koszykówki nie jest w stanie nauczyć się wartości jego poszczególnych przedmiotów? – user12345613

+1

po stwierdzeniu "Nie pytaj" - uwaga wydaje się zła. – Tom

+0

Gdzie usłyszałeś zasadę "powiedz, nie pytaj"? Nigdy o tym nie słyszałem. – xxpor

Odpowiedz

7

Jeśli nie używasz żądane dane/stan zmienić przedmiot masz na myśli, nie sądzę, nie ma nic złego w używaniu pobierające w ogóle .

Zasada mówi o scenariuszu, takie jak ten:

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

To powinno stać się czymś jak $item->set_minimum_price(5).

4

Istnieją dwie pozycje z artykułu „Tell, Don't Ask” masz na myśli, że ponoszą dokładnego zbadania:

  1. [...] nie pytaj [obiekty] pytania na temat ich stanu, podjąć decyzję , a następnie powiedz im, co mają robić.
    W tym konkretnym przykładzie, chopping_cart wysyła tylko zapytania do obiektów, które podejmuje &. Co najważniejsze, nie mówi im, co mają robić.
  2. Zgodnie z projektem według umowy, o ile twoje metody (zapytania i polecenia) mogą być swobodnie wymieszane i nie ma sposobu, aby naruszyć niezmienniki klasy, robiąc to, wtedy jesteś w porządku. Ale podczas gdy zachowujesz niezmienną klasę, możesz również znacznie zwiększyć łączność między osobą dzwoniącą a osobą odbierającą w zależności od tego, jak bardzo jesteś narażony.
    Połączenia Gettera można ogólnie dowolnie wymieszać, więc nie ma sprzężenia spowodowanego koniecznością zachowania niezmienników klas.

W ten sposób gettery nie powodują problemów, które ma zapobiegać zasada "powiedz, nie pytaj".

2

Można użyć Visitor design pattern w tym przypadku. W swojej klasie Product zaimplementuj metodę addToBill i jako argument przeprowadź instancję implementującą interfejs twojego rachunku, IBill. IBill obsługuje metodę addToTotal, która będzie akceptować wszystkie potrzebne informacje dostępne w elemencie; w twoim przypadku jest to cena. Na przykład:

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

Jednak zawsze lądujesz na chybotliwym terenie. Powyższe wymaga metody, takiej jak addToTotal, która wprowadza niezmiennik (suma musi pasować do sumy produktów ceny i ilości elementów zamówienia), należy unikać takich rzeczy, jak "Powiedz, nie pytaj". Możesz spróbować zrobić to bez addToTotal: * Zlikwiduj numer Bill; ShoppingCart śledzi całkowitą. Przekaż cenę do addItem oprócz produktu & ilość; addItem aktualizuje sumę. To nieco pogarsza cel posiadania klas, ponieważ nie używasz zbyt wiele.Dodaje to również niezmienność, że przekazana cena i cena podane podczas tworzenia produktu powinny być zgodne, ale jeśli nie, to nie powinno powodować problemów (byłoby to po prostu dziwne). * Czy addItem utworzyć instancję Product i LineItem; addItem aktualizuje sumę. Podczas dodawania dodatkowych elementów, które zostały wcześniej dodane, musi istnieć dodatkowy niezmiennik, który musi być zgodny z wartością przekazaną w poprzednich połączeniach, lub addItem po prostu nie może dodawać dodatkowych, istniejących elementów. * Pozbądź się wszystkich razem. ShoppingCart przechowuje identyfikator produktu i ilość. Każde połączenie z numerem addItem aktualizuje sumę. createBill używa już obliczonej sumy. Nawet więcej niż inne, to jednoczy separate concerns.

Istnieją inne potencjalne projekty, ale każdy cierpi z powodu jakiegoś problemu, zazwyczaj związanego z separacją obaw, wprowadzaniem niezmienników i zwiększaniem złożoności. Łącznie dostęp do całkowitej ceny elementu zamówienia bezpośrednio w metodzie, która oblicza sumę, jest nie tylko najprostszy, ale najczystszy i najmniej prawdopodobny do wygenerowania błędów.