Apakah ide yang baik untuk mendefinisikan satu fungsi pribadi besar di kelas untuk mempertahankan status yang valid, yaitu, untuk memperbarui anggota data objek?

18

Meskipun dalam kode di bawah ini digunakan pembelian barang tunggal sederhana dalam situs e-commerce, pertanyaan umum saya adalah tentang memperbarui semua anggota data untuk menjaga data objek dalam keadaan valid setiap saat.

Saya menemukan "konsistensi" dan "negara itu jahat" sebagai frasa yang relevan, dibahas di sini: 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();

Adakah kerugian, atau mungkin cara yang lebih baik untuk melakukan ini?

Ini adalah masalah berulang dalam aplikasi dunia nyata yang didukung oleh basis data relasional, dan jika Anda tidak menggunakan prosedur tersimpan secara ekstensif untuk mendorong semua validasi ke dalam basis data. Saya pikir bahwa penyimpanan data harus hanya menyimpan data, sementara kode harus melakukan semua kondisi run-time.

EDIT: ini adalah pertanyaan terkait tetapi tidak memiliki rekomendasi praktik terbaik mengenai fungsi besar tunggal untuk mempertahankan status yang valid: /programming/1122346/c-sharp-object-oriented-design-maintaining- valid-object-state

EDIT2: Meskipun jawaban @ eignesheep adalah yang terbaik, jawaban ini - /software//a/148109/208591 - adalah apa yang mengisi garis antara jawaban @ eigensheep dan apa yang ingin saya ketahui - kode hanya boleh diproses, dan negara global harus diganti dengan lewatnya keadaan yang diaktifkan oleh DI di antara objek.

site80443
sumber
Saya menghindari memiliki variabel yang persentase. Anda dapat menerima persentase dari pengguna, atau menampilkan satu ke pengguna, tetapi hidup jauh lebih baik jika variabel program adalah rasio.
kevin cline

Jawaban:

29

Semuanya sama, Anda harus mengekspresikan invarian Anda dalam kode. Dalam hal ini Anda memiliki invarian

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

Untuk mengekspresikan ini dalam kode Anda, hapus variabel anggota pajak dan ganti getTaxAmt () dengan

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

Anda harus melakukan sesuatu yang serupa untuk menghilangkan variabel total biaya anggota.

Mengekspresikan invarian Anda dalam kode Anda dapat membantu menghindari bug. Dalam kode asli, total biaya tidak benar jika diperiksa sebelum setPrice atau setShipping dipanggil.

eigensheep
sumber
3
Banyak bahasa memiliki getter sehingga fungsi seperti itu akan berpura-pura sebagai properti. Yang terbaik dari keduanya!
curiousdannii
Poin yang sangat baik, tetapi kasus penggunaan umum saya adalah di mana kode mengambil dan menyimpan data ke beberapa kolom dalam beberapa tabel dalam database relasional (kebanyakan MySQL) dan saya tidak ingin menggunakan prosedur yang tersimpan (dapat diperdebatkan dan topik lain sendiri). Mengambil ide invarian-in-code Anda lebih lanjut ini berarti bahwa semua perhitungan harus "dirantai": getTotalCost()panggilangetTaxAmt() dan sebagainya. Ini artinya kita hanya menyimpan barang yang tidak dihitung . Apakah kita bergerak sedikit ke arah pemrograman fungsional? Ini juga mempersulit penyimpanan entitas yang dihitung dalam tabel untuk akses cepat ... Membutuhkan eksperimen!
site80443
13

Kerugian [?]

Tentu. Metode ini bergantung pada semua orang yang selalu ingat untuk melakukan sesuatu. Metode apa pun yang mengandalkan semua orang & selalu gagal kadang-kadang.

mungkin cara yang lebih baik untuk melakukan ini?

Salah satu cara untuk menghindari beban upacara mengingat adalah menghitung properti objek yang bergantung pada properti lain sesuai kebutuhan, seperti yang disarankan @eigensheep.

Lain adalah membuat item keranjang tidak dapat diubah dan menghitungnya dalam konstruktor / metode pabrik. Anda biasanya akan pergi dengan metode "menghitung sesuai kebutuhan", bahkan jika Anda membuat objek tidak berubah. Tetapi jika perhitungannya terlalu memakan waktu dan akan dibaca berkali-kali; Anda dapat memilih opsi "menghitung selama pembuatan".

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

Anda harus bertanya pada diri sendiri; Apakah barang keranjang tanpa harga masuk akal? Bisakah harga suatu barang berubah? Setelah dibuat? Setelah pajaknya dihitung? dll. Mungkin Anda harus membuat CartItemharga dan pengiriman yang tidak berubah dan penting dalam konstruktor:

$i = new CartItem(100, 20);

Apakah item keranjang masuk akal tanpa kereta miliknya?

Jika tidak, saya harapkan $cart->addItem(100, 20)sebagai gantinya.

abuzittin gillifirca
sumber
3
Anda menunjukkan kerugian terbesar: mengandalkan orang-orang yang ingat melakukan sesuatu jarang merupakan solusi yang baik. Satu-satunya hal yang dapat Anda andalkan pada manusia untuk dilakukan adalah mereka akan lupa melakukan sesuatu.
corsiKa
@corsiKlause Ho Ho Ho dan abuzittin, poin yang kuat, tidak dapat berdebat dengan itu - orang selalu lupa . Namun, kode yang saya tulis di atas hanyalah sebuah contoh, ada kasus penggunaan substansial di mana beberapa anggota data diperbarui kemudian. Cara lain yang saya lihat adalah menormalkan lebih lanjut - membuat kelas sedemikian rupa sehingga anggota data yang diperbarui secara independen berada di kelas lain dan mendorong tanggung jawab pembaruan ke beberapa antarmuka - sehingga programmer lain (dan Anda sendiri setelah beberapa waktu) harus menulis sebuah metode - metode kompiler mengingatkan Anda bahwa Anda harus menulisnya. Tapi itu akan menambah lebih banyak kelas ...
site80443
1
@ site80443 Berdasarkan apa yang saya lihat, itu pendekatan yang salah. Cobalah memodelkan data Anda sedemikian rupa sehingga hanya data yang divalidasi terhadapnya yang disertakan. Misalnya harga suatu barang tidak bisa negatif hanya bergantung pada dirinya sendiri. Jika item didiskon, jangan faktor diskon ke dalam harga - hiasi dengan diskon nanti. Simpan $ 4,99 untuk item dan diskon 20% sebagai entitas yang terpisah, dan pajak 5% sebagai entitas lain. Sebenarnya Anda harus mempertimbangkan pola Penghias jika contoh-contoh tersebut mewakili kode kehidupan nyata Anda.
corsiKa