È una buona idea definire una grande funzione privata in una classe per mantenere uno stato valido, ovvero aggiornare i membri dei dati dell'oggetto?


18

Sebbene nel codice riportato di seguito venga utilizzato un semplice acquisto di un singolo articolo in un sito di e-commerce, la mia domanda generale riguarda l'aggiornamento di tutti i membri dei dati per mantenere sempre validi i dati di un oggetto.

Ho trovato "coerenza" e "stato è il male" come frasi pertinenti, discusse qui: 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();

Svantaggi o forse modi migliori per farlo?

Questo è un problema ricorrente nelle applicazioni del mondo reale supportate da un database relazionale e se non si utilizzano estesamente le procedure memorizzate per inviare tutta la convalida nel database. Penso che l'archivio dati dovrebbe semplicemente archiviare i dati, mentre il codice dovrebbe fare tutto lo stato di runtime mantenendo il lavoro.

EDIT: questa è una domanda correlata ma non ha una raccomandazione di buone pratiche riguardo a un'unica grande funzione per mantenere lo stato valido: /programming/1122346/c-sharp-object-oriented-design-maintain- valido-oggetto-state

EDIT2: sebbene la risposta di @ eignesheep sia la migliore, questa risposta - /software//a/148109/208591 - è ciò che riempie le linee tra la risposta di @ eigensheep e quello che volevo sapere - il codice dovrebbe solo elaborare, e lo stato globale dovrebbe essere sostituito dal passaggio di stato abilitato DI tra oggetti.


Evito di avere variabili che sono percentuali. Puoi accettare una percentuale dall'utente o visualizzarne una a un utente, ma la vita è molto meglio se le variabili del programma sono rapporti.
Kevin Cline,

Risposte:


29

A parità di tutto il resto, dovresti esprimere i tuoi invarianti in codice. In questo caso hai l'invariante

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

Per esprimerlo nel tuo codice, rimuovi la variabile del membro fiscale e sostituisci getTaxAmt () con

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

Si dovrebbe fare qualcosa di simile per sbarazzarsi della variabile membro costo totale.

Esprimere i tuoi invarianti nel tuo codice può aiutare a evitare bug. Nel codice originale, il costo totale non è corretto se verificato prima di chiamare setPrice o setShipping.


3
Molte lingue hanno getter in modo che tali funzioni fingano di essere proprietà. Il meglio di entrambi!
curiousdannii,

Punto eccellente, ma il mio caso d'uso generale è quello in cui il codice recupera e archivia i dati su più colonne in più tabelle in un database relazionale (principalmente MySQL) e non voglio usare le procedure memorizzate (discutibili e un altro argomento a sé stante). Portare ulteriormente la tua idea di invarianti nel codice significa che tutti i calcoli devono essere "concatenati": getTotalCost()chiamate getTaxAmt()e così via. Ciò significa che memorizziamo solo cose non calcolate . Ci stiamo muovendo un po 'verso la programmazione funzionale? Ciò complica anche la memorizzazione di entità calcolate nelle tabelle per un accesso rapido ... Ha bisogno di sperimentazione!
site 80443,

13

Eventuali svantaggi [?]

Sicuro. Questo metodo si basa sul fatto che tutti ricordano sempre di fare qualcosa. Qualsiasi metodo basato su tutti e sempre è destinato a fallire a volte.

forse modi migliori per farlo?

Un modo per evitare l'onere di ricordare la cerimonia è il calcolo delle proprietà dell'oggetto che dipendono da altre proprietà secondo necessità, come suggerito da @eigensheep.

Un altro è rendere immutabile l'articolo del carrello e calcolarlo nel metodo costruttore / metodo di fabbrica. Normalmente andresti con il metodo "calcola secondo necessità", anche se rendessi immutabile l'oggetto. Ma se il calcolo richiede troppo tempo e verrebbe letto molte, molte volte; puoi scegliere l'opzione "calcola durante la creazione".

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

Dovresti chiederti; Ha senso un articolo senza carrello? Il prezzo di un articolo può cambiare? Dopo che è stato creato? Dopo aver calcolato le tasse? ecc. Forse dovresti rendere CartItemimmutabili e assig il prezzo e la spedizione nel costruttore:

$i = new CartItem(100, 20);

Un articolo del carrello ha senso senza il carrello a cui appartiene?

Altrimenti, mi aspetterei $cart->addItem(100, 20)invece.


3
Indichi il più grande svantaggio: affidarsi alle persone che ricordano di fare le cose raramente è una buona soluzione. L'unica cosa che puoi fare affidamento su un essere umano è che dimenticheranno di fare qualcosa.
corsiKa

@corsiKlause Ho Ho Ho e abuzittin, punto solido, non possono discutere con questo - la gente dimentica invariabilmente . Tuttavia, il codice che ho scritto sopra è solo un esempio, ci sono casi d'uso sostanziali in cui alcuni membri dei dati vengono aggiornati in seguito. L'altro modo in cui vedo è di normalizzare ulteriormente - creare classi in modo tale che i membri dei dati aggiornati in modo indipendente siano in altre classi e spingere la responsabilità dell'aggiornamento su alcune interfacce - in modo che altri programmatori (e te stesso dopo un po 'di tempo) debbano scrivere un metodo: il compilatore ti ricorda che devi scriverlo. Ma ciò aggiungerebbe molte più classi ...
site80443

1
@ site80443 Sulla base di quello che vedo, questo è l'approccio sbagliato. Prova a modellare i tuoi dati in modo tale da includere solo i dati validati rispetto a se stesso. Ad esempio, il prezzo di un articolo non può essere negativo dipende solo da se stesso. Se un articolo è scontato, non considerare lo sconto nel prezzo: decoralo con uno sconto in seguito. Conservare $ 4,99 per l'articolo e lo sconto del 20% come entità separata e l'imposta del 5% come ancora un'altra entità. In realtà sembra che dovresti considerare il modello Decorator se gli esempi rappresentano il tuo codice di vita reale.
corsiKa
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.