在类中定义一个大型私有函数以保持有效状态(即更新对象的数据成员)是否是一个好主意?


18

尽管在下面的代码中使用的是在电子商务站点中简单的单项购买,但我的一般问题是有关更新所有数据成员以始终保持对象数据处于有效状态的信息。

我发现“一致性”和“国家是邪恶的”是相关的短语,在这里进行了讨论: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();

有什么缺点,或者更好的方法吗?

在由关系数据库支持的实际应用程序中,这是一个经常发生的问题,如果您不大量使用存储过程来将所有验证都推入数据库中。我认为数据存储区应该只存储数据,而代码应该执行所有运行时状态维护工作。

编辑:这是一个相关的问题,但没有关于维护有效状态的单个大功能的最佳实践建议:https : //stackoverflow.com/questions/1122346/c-sharp-object-directional-design-maintaining-有效对象状态

EDIT2:虽然@ eignesheep的回答是最好的,这答案- /software//a/148109/208591 -是什么罢了@ eigensheep的答案,我想知道之间的界限-代码应该只处理,全局状态应由启用DI的对象之间的状态传递代替。


我避免使用百分比变量。您可以接受用户的百分比,也可以向用户显示一个百分比,但是如果程序变量是比率,则寿命会更好。
凯文·克莱恩

Answers:


29

在所有其他条件相同的情况下,您应该在代码中表达您的不变式。在这种情况下,你有不变的

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

要在您的代码中表达这一点,请删除纳税成员变量,然后将getTaxAmt()替换为

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

您应该执行类似的操作来摆脱总成本成员变量。

在代码中表达不变量可以帮助避免错误。在原始代码中,如果在调用setPrice或setShipping之前检查总费用是不正确的。


3
许多语言都有吸气剂,因此这些函数会假装它们是属性。两者兼具!
curiousdannii 2015年

很好,但我的一般用例是代码获取数据并将数据存储到关系数据库(主要是MySQL)的多个表中的多列中,而我不想使用存储过程(值得商and的问题和另一个话题)。进一步采用代码不变式的想法,这意味着所有计算都必须“链接”:getTotalCost()调用getTaxAmt()等。这意味着我们只会存储未计算的东西。我们是否正在朝函数式编程发展?这也使将已计算实体存储在表中以使快速访问变得复杂。
site80443 2015年

13

有什么缺点[?]

当然。这种方法依赖于每个人总是记得做某事。任何依赖于所有人的方法有时总是会失败

也许更好的方法来做到这一点?

避免记忆的负担的一种方法是根据@eigensheep的建议,根据需要计算依赖于其他属性的对象的属性。

另一个方法是使购物车项目不变​​,并在构造函数/工厂方法中对其进行计算。即使您使对象不可变,通常也可以使用“按需计算”方法。但是,如果计算太费时,将被读取很多次;您可以选择“在创建期间计算”选项。

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

你应该问问自己;没有价格的购物车有意义吗?物品的价格可以改变吗?创建之后?计算完税后?等也许你应该CartItem在构造函数中使价格不变并增加价格:

$i = new CartItem(100, 20);

没有推车所属的推车是否有意义?

如果没有的话,我期望可以$cart->addItem(100, 20)代替。


3
您指出了最大的缺点:依靠记住做事的人很少是一个好的解决方案。关于人类,您唯一可以依靠的就是他们会忘记做某事。
corsiKa 2015年

@corsiKlause Ho Ho Ho和abuzittin确实不能否认这一点-人们总是忘记。但是,我上面编写的代码仅是一个示例,在一些实际用例中,某些数据成员稍后会更新。我看到的另一种方法是进一步规范化-创建类,使独立更新的数据成员位于其他类中,并将更新的职责推到某些接口上-以便其他程序员(以及一段时间后的您自己)必须编写一个方法-编译器提醒您必须编写它。但这会增加更多的类……
site80443 2015年

1
@ site80443根据我所看到的,这是错误的方法。尝试对数据建模,以便仅包括针对自身进行验证的数据。例如,一件物品的价格不能为负,只能依靠它自己。如果某件商品打折,请不要在价格中考虑折扣-稍后用折扣装饰。将项目的4.99美元和20%的折扣存储为单独的实体,并将5%的税存储为另一个实体。实际上,如果这些示例代表您的实际代码,则您似乎应该考虑使用Decorator模式。
corsiKa 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.