Aunque en el siguiente código se usa una compra simple de un solo artículo en un sitio de comercio electrónico, mi pregunta general es sobre la actualización de todos los miembros de datos para mantener los datos de un objeto en estado válido en todo momento.
Encontré "consistencia" y "estado es malo" como frases relevantes, discutidas aquí: 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();
¿Alguna desventaja, o quizás mejores maneras de hacer esto?
Este es un problema recurrente en las aplicaciones del mundo real respaldadas por una base de datos relacional, y si no utiliza los procedimientos almacenados de forma exhaustiva para insertar toda la validación en la base de datos. Creo que el almacén de datos solo debería almacenar datos, mientras que el código debería hacer todo el estado de tiempo de ejecución manteniendo el trabajo.
EDITAR: esta es una pregunta relacionada, pero no tiene una recomendación de mejores prácticas con respecto a una sola función grande para mantener un estado válido: /programming/1122346/c-sharp-object-oriented-design-maintaining- estado-objeto-válido
EDIT2: Aunque la respuesta de @ eignesheep es la mejor, esta respuesta - /software//a/148109/208591 - es lo que llena las líneas entre la respuesta de @ eigensheep y lo que quería saber: el código solo debe procesarse, y el estado global debe sustituirse por el paso de estado habilitado por DI entre objetos.
fuente
Respuestas:
En igualdad de condiciones, debe expresar sus invariantes en código. En este caso tienes la invariante
Para expresar esto en su código, elimine la variable de miembro de impuestos y reemplace getTaxAmt () con
Debe hacer algo similar para deshacerse de la variable de miembro de costo total.
Expresar sus invariantes en su código puede ayudar a evitar errores. En el código original, el costo total es incorrecto si se marca antes de llamar a setPrice o setShipping.
fuente
getTotalCost()
llamadas,getTaxAmt()
etc. Esto significa que solo almacenamos cosas no calculadas . ¿Nos estamos moviendo un poco hacia la programación funcional? Esto también complica el almacenamiento de entidades calculadas en tablas para un acceso rápido ... ¡Necesita experimentación!Seguro. Este método se basa en que todos siempre recuerden hacer algo. Cualquier método que dependa de todos y siempre siempre fallará .
Una forma de evitar la carga de recordar la ceremonia es calcular las propiedades del objeto que dependen de otras propiedades según sea necesario, como sugirió @eigensheep.
Otro es hacer que el artículo del carro sea inmutable y calcularlo en el método del constructor / fábrica. Normalmente iría con el método "calcular según sea necesario", incluso si hizo que el objeto sea inmutable. Pero si el cálculo lleva demasiado tiempo y se leería muchas, muchas veces; puede elegir la opción "calcular durante la creación".
Deberías preguntarte a ti mismo; ¿Tiene sentido un artículo de carrito sin precio? ¿Puede cambiar el precio de un artículo? Después de que se crea? Después de su impuesto calculado? etc. Tal vez debería hacer que el
CartItem
precio y el envío sean inmutables y asignados en el constructor:¿Tiene sentido un artículo de carrito sin el carrito al que pertenece?
Si no, esperaría en su
$cart->addItem(100, 20)
lugar.fuente