È 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.