Czy dobrym pomysłem jest zdefiniowanie jednej dużej prywatnej funkcji w klasie w celu utrzymania prawidłowego stanu, czyli aktualizacji elementów danych obiektu?

18

Chociaż w poniższym kodzie użyto prostego zakupu pojedynczego elementu w witrynie e-commerce, moje ogólne pytanie dotyczy aktualizacji wszystkich członków danych, aby dane obiektu były zawsze aktualne.

Znalazłem „spójność” i „stan jest zły” jako odpowiednie wyrażenia, omówione tutaj: https://en.wikibooks.org/wiki/Object_Oriented_Programming#.22State.22_is_Evil.21

<?php

class CartItem {
  private $price = 0;
  private $shipping = 5; // default
  private $tax = 0;
  private $taxPC = 5; // fixed
  private $totalCost = 0;

  /* private function to update all relevant data members */
  private function updateAllDataMembers() {
    $this->tax =  $this->taxPC * 0.01 * $this->price;
    $this->totalCost = $this->price + $this->shipping + $this->tax;
  }

  public function setPrice($price) {
      $this->price = $price;
      $this->updateAllDataMembers(); /* data is now in valid state */
  }

  public function setShipping($shipping) {
    $this->shipping = $shipping;
    $this->updateAllDataMembers(); /* call this in every setter */
  }

  public function getPrice() {
    return $this->price;
  }
  public function getTaxAmt() {
    return $this->tax;
  }
  public function getShipping() {
    return $this->shipping;
  }
  public function getTotalCost() {
    return $this->totalCost;
  }
}
$i = new CartItem();
$i->setPrice(100);
$i->setShipping(20);
echo "Price = ".$i->getPrice(). 
  "<br>Shipping = ".$i->getShipping().
  "<br>Tax = ".$i->getTaxAmt().
  "<br>Total Cost = ".$i->getTotalCost();

Jakieś wady, a może lepsze sposoby na zrobienie tego?

Jest to powtarzający się problem w rzeczywistych aplikacjach wspieranych przez relacyjną bazę danych, a jeśli nie korzystasz z procedur przechowywanych w celu wypchnięcia całej weryfikacji do bazy danych. Myślę, że magazyn danych powinien po prostu przechowywać dane, podczas gdy kod powinien wykonywać cały stan wykonywania pracy utrzymując pracę.

EDYCJA: jest to powiązane pytanie, ale nie ma zalecenia najlepszych praktyk dotyczących jednej dużej funkcji do utrzymania prawidłowego stanu: /programming/1122346/c-sharp-object-oriented-design-maintaining- valid-object-state

EDIT2: Chociaż odpowiedź @ eignesheep jest najlepsza, ta odpowiedź - /software//a/148109/208591 - wypełnia linie między odpowiedzią @ eigensheep a tym, co chciałem wiedzieć - kod powinien tylko przetwarzać, a stan globalny powinien zostać zastąpiony przekazywaniem stanu między obiektami przez DI.

site80443
źródło
Unikam zmiennych, które są procentami. Możesz zaakceptować procent od użytkownika lub wyświetlić go użytkownikowi, ale życie jest znacznie lepsze, jeśli zmienne programu są stosunkami.
kevin cline

Odpowiedzi:

29

Wszystkie pozostałe są równe, powinieneś wyrazić niezmienniki w kodzie. W tym przypadku masz niezmiennik

$this->tax =  $this->taxPC * 0.01 * $this->price;

Aby wyrazić to w kodzie, usuń zmienną podatkową i zamień getTaxAmt () na

public function getTaxAmt() {
  return $this->taxPC * 0.01 * $this->price;
}

Powinieneś zrobić coś podobnego, aby pozbyć się zmiennej członka kosztu całkowitego.

Wyrażenie niezmienników w kodzie może pomóc uniknąć błędów. W oryginalnym kodzie całkowity koszt jest niepoprawny, jeśli zostanie sprawdzony przed wywołaniem setPrice lub setShipping.

eigensheep
źródło
3
Wiele języków ma moduły pobierające, więc takie funkcje będą udawać, że są właściwościami. Najlepsze z obu!
curiousdannii
Doskonały punkt, ale mój ogólny przypadek użycia polega na tym, że kod pobiera i przechowuje dane do wielu kolumn w wielu tabelach w relacyjnej bazie danych (głównie MySQL) i nie chcę używać procedur przechowywanych (debatujących i innych na własną rękę). Kontynuacja idei niezmienników w kodzie oznacza, że ​​wszystkie obliczenia muszą być „powiązane”: getTotalCost()wywołania getTaxAmt()i tak dalej. Oznacza to, że zawsze przechowujemy tylko rzeczy nieobliczone . Czy przechodzimy trochę w stronę programowania funkcjonalnego? To także komplikuje przechowywanie obliczonych jednostek w tabelach w celu szybkiego dostępu ... Potrzebuje eksperymentów!
site80443,
13

Wszelkie wady [?]

Pewnie. Ta metoda polega na tym, że każdy zawsze pamięta o zrobieniu czegoś. Każda metoda polegająca na wszystkich i zawsze zawiedzie się czasami.

może lepsze sposoby to zrobić?

Jednym ze sposobów uniknięcia ciężaru zapamiętywania ceremonii jest obliczanie właściwości obiektu, które zależą od innych właściwości w razie potrzeby, jak sugeruje @eigensheep.

Kolejnym jest unieruchomienie koszyka i obliczenie go w metodzie konstruktora / fabryce. Zwykle wybrałbyś metodę „oblicz w razie potrzeby”, nawet jeśli uczynisz obiekt niezmiennym. Ale jeśli obliczenia są zbyt czasochłonne i zostaną odczytane wiele, wiele razy; możesz wybrać opcję „oblicz podczas tworzenia”.

$i = new CartItem();
$i->setPrice(100);
$i->setShipping(20);

Powinieneś zadać sobie pytanie; Czy produkt w koszyku bez ceny ma sens? Czy cena przedmiotu może się zmienić? Po utworzeniu? Po obliczeniu podatku? itp. Może powinieneś zrobić CartItemniezmienną i przypisać cenę i wysyłkę do konstruktora:

$i = new CartItem(100, 20);

Czy produkt w koszyku ma sens bez koszyka, do którego należy?

Jeśli nie, oczekiwałbym $cart->addItem(100, 20)zamiast tego.

abuzittin gillifirca
źródło
3
Wskazujesz na największą wadę: poleganie na ludziach, którzy pamiętają o robieniu rzeczy, rzadko jest dobrym rozwiązaniem. Jedyną rzeczą, na której możesz polegać, jest to, że człowiek zapomni coś zrobić.
corsiKa
@corsiKlause Ho Ho Ho i abuzittin, solidny punkt, nie mogą się z tym kłócić - ludzie zawsze zapominają . Jednak kod, który napisałem powyżej, jest tylko przykładem, istnieją poważne przypadki użycia, w których niektórzy członkowie danych są aktualizowani później. Innym sposobem, który widzę, jest dalsza normalizacja - tworzenie klas tak, aby niezależnie zaktualizowane elementy danych były w innych klasach, i nałożenie odpowiedzialności za aktualizację na niektóre interfejsy - aby inni programiści (i ty po pewnym czasie) musieli napisać metodę - kompilator przypomina, że ​​musisz go napisać. Ale to
dodałoby
1
@ site80443 Na podstawie tego, co widzę, jest to złe podejście. Spróbuj modelować swoje dane w taki sposób, aby uwzględniono tylko dane, które są sprawdzone pod kątem ich własnej wartości. Na przykład cena przedmiotu nie może być ujemna, zależy tylko od siebie. Jeśli przedmiot jest dyskontowany, nie uwzględniaj rabatu w cenie - udekoruj go zniżką później. Przechowuj 4,99 USD za przedmiot i rabat 20% jako osobny podmiot, a podatek 5% jako kolejny podmiot. Wygląda na to, że powinieneś rozważyć wzorzec Dekoratora, jeśli przykłady reprezentują Twój rzeczywisty kod.
corsiKa,