Embora no código abaixo seja usada uma compra simples de um item em um site de comércio eletrônico, minha pergunta geral é sobre a atualização de todos os membros de dados para manter os dados de um objeto em estado válido o tempo todo.
Encontrei "consistência" e "estado é mau" como frases relevantes, discutidas aqui: 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();
Alguma desvantagem, ou talvez melhores maneiras de fazer isso?
Esse é um problema recorrente em aplicativos do mundo real apoiados por um banco de dados relacional e se você não usar procedimentos armazenados extensivamente para enviar toda a validação para o banco de dados. Eu acho que o armazenamento de dados deve apenas armazenar dados, enquanto o código deve executar todo o estado de tempo de execução, mantendo o trabalho.
EDIT: esta é uma pergunta relacionada, mas não possui uma recomendação de boas práticas em relação a uma única função grande para manter o estado válido: /programming/1122346/c-sharp-object-oriented-design-maintaining- estado-objeto-válido
EDIT2: Embora a resposta da @ eignesheep seja a melhor, esta resposta - /software//a/148109/208591 - é o que preenche as linhas entre a resposta da @ eigensheep e o que eu queria saber - o código só deve processar, e o estado global deve ser substituído pela passagem de estado habilitada para DI entre os objetos.
fonte
Respostas:
Sendo tudo igual, você deve expressar seus invariantes em código. Nesse caso, você tem o invariante
Para expressar isso em seu código, remova a variável do membro do imposto e substitua getTaxAmt () por
Você deve fazer algo semelhante para se livrar da variável de membro de custo total.
Expressar seus invariantes em seu código pode ajudar a evitar erros. No código original, o custo total está incorreto se marcado antes de chamar setPrice ou setShipping.
fonte
getTotalCost()
chamadasgetTaxAmt()
e assim por diante. Isso significa que nós sempre armazenamos coisas não calculadas . Estamos nos movendo um pouco para a programação funcional? Isso também complica o armazenamento de entidades calculadas em tabelas para acesso rápido ... Precisa de experimentação!Certo. Esse método depende de todos sempre se lembrando de fazer algo. Qualquer método que confie em todos e sempre está sujeito a falhar às vezes.
Uma maneira de evitar o ônus da lembrança da cerimônia é calcular as propriedades do objeto que dependem de outras propriedades, conforme necessário, como sugeriu @eigensheep.
Outra é tornar o item do carrinho imutável e calculá-lo no método construtor / fábrica. Você normalmente usaria o método "calcular conforme necessário", mesmo se você tornasse o objeto imutável. Mas se o cálculo consumir muito tempo e for lido muitas e muitas vezes; você pode escolher a opção "calcular durante a criação".
Você deveria se perguntar; Um item do carrinho sem preço faz sentido? O preço de um item pode mudar? Depois de criado? Após o imposto calculado? etc Talvez você deva tornar
CartItem
imutável e atribuir preço e frete no construtor:Um item do carrinho faz sentido sem o carrinho ao qual pertence?
Se não, eu esperaria
$cart->addItem(100, 20)
.fonte