Является ли хорошей идеей определить одну большую частную функцию в классе для поддержания действительного состояния, то есть обновить элементы данных объекта?

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();

Есть ли недостатки, или, может быть, лучшие способы сделать это?

Это повторяющаяся проблема в реальных приложениях, поддерживаемых реляционной базой данных, и если вы не используете хранимые процедуры, чтобы перенести всю проверку в базу данных. Я думаю, что хранилище данных должно просто хранить данные, а код должен выполнять всю работу по поддержанию состояния во время выполнения.

РЕДАКТИРОВАТЬ: это связанный вопрос, но он не имеет рекомендации передового опыта в отношении одной большой функции для поддержания действительного состояния: /programming/1122346/c-sharp-object-oriented-design-maintenance- действует объектно-состояние

РЕДАКТИРОВАТЬ 2: Хотя ответ @ eignesheep является лучшим, этот ответ - /software//a/148109/208591 - это то, что заполняет грань между ответом @ eigensheep и тем, что я хотел знать - код должен только обрабатываться, и глобальное состояние должно быть заменено передачей состояния с поддержкой DI между объектами.

site80443
источник
Я избегаю переменных, которые являются процентами. Вы можете принять процент от пользователя или отобразить его для пользователя, но жизнь намного лучше, если переменные программы - это отношения.
Кевин Клайн

Ответы:

29

При прочих равных вы должны выразить свои инварианты в коде. В этом случае у вас есть инвариант

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

Чтобы выразить это в своем коде, удалите переменную налогового члена и замените getTaxAmt () на

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

Вы должны сделать что-то похожее, чтобы избавиться от переменной общей стоимости.

Выражение ваших инвариантов в вашем коде может помочь избежать ошибок. В исходном коде общая стоимость неверна, если проверяется перед вызовом setPrice или setShipping.

eigensheep
источник
3
Многие языки имеют геттеры, поэтому такие функции будут притворяться, что они являются свойствами. Лучшее из обоих!
curiousdannii
Отличный момент, но мой общий случай использования - это когда код выбирает и сохраняет данные в несколько столбцов в нескольких таблицах в реляционной базе данных (в основном, в MySQL), и я не хочу использовать хранимые процедуры (дискуссионный и другой вопрос сам по себе). Продолжая идею инвариантов в коде, это означает, что все вычисления должны быть «связаны»: getTotalCost()вызовы getTaxAmt()и так далее. Это означает, что мы храним только не рассчитанные вещи . Двигаемся ли мы немного к функциональному программированию? Это также усложняет хранение вычисляемых объектов в таблицах для быстрого доступа ... Требуется эксперимент!
site80443
13

Есть ли недостатки [?]

Конечно. Этот метод полагается на то, что все всегда помнят что-то сделать. Любой метод, опирающийся на всех и всегда, иногда обречен на провал .

может быть, лучшие способы сделать это?

Одним из способов избежать бремени запоминания церемонии является вычисление свойств объекта, которые по мере необходимости зависят от других свойств, как предположил @eigensheep.

Другой способ - сделать элемент корзины неизменным и рассчитать его в конструкторе / фабричном методе. Обычно вы используете метод «Рассчитать по мере необходимости», даже если вы сделали объект неизменным. Но если расчет занимает слишком много времени и будет прочитан много-много раз; Вы можете выбрать опцию «Рассчитать при создании».

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

Вы должны спросить себя; Имеет ли смысл корзина без цены? Может ли цена товара измениться? После того, как это создано? После того, как его налог рассчитывается? и т. д. Может быть, вы должны сделать CartItemнеизменную цену и доставку в конструкторе:

$i = new CartItem(100, 20);

Имеет ли смысл корзина без корзины, к которой она принадлежит?

Если нет, я бы ожидал $cart->addItem(100, 20)вместо этого.

Abuzittin Gillifirca
источник
3
Вы указываете на самый большой недостаток: полагаться на людей, помнящих что-то, редко бывает хорошим решением. Единственное, на что вы можете рассчитывать, это то, что люди забудут что-то сделать.
CorsiKa
@corsiKlause Ho Ho Ho и abuzittin, сплошная точка зрения, не могут с этим поспорить - люди неизменно забывают . Тем не менее, код, который я написал выше, является только примером, есть существенные случаи использования, когда некоторые члены данных обновляются позже. Другой способ, который я вижу, - это дальнейшая нормализация - сделать классы такими, чтобы независимо обновляемые члены данных находились в других классах, и перенести ответственность за обновление на некоторые интерфейсы - так, чтобы другие программисты (и вы сами через некоторое время) должны были написать метод - Компилятор напоминает вам, что вы должны написать это. Но это добавило бы намного больше классов ...
site80443
1
@ site80443 Судя по тому, что я вижу, это неправильный подход. Попробуйте смоделировать ваши данные таким образом, чтобы в них были включены только те данные, которые были проверены на их соответствие. Например, цена товара не может быть отрицательной, зависит только от самого себя. Если предмет уценен, не учитывайте скидку в цене - украсьте ее скидкой позже. Сохраните $ 4,99 за товар и скидку 20% как отдельное предприятие, а налог 5% - как еще одно предприятие. На самом деле похоже, что вы должны рассмотреть шаблон Decorator, если примеры представляют ваш реальный код.
CorsiKa