Apakah Pola negara melanggar Prinsip Substitusi Liskov?

17

Gambar ini diambil dari Menerapkan Desain dan Pola Berbasis Domain: Dengan Contoh di C # dan .NET

masukkan deskripsi gambar di sini

Ini adalah diagram kelas untuk Pola Negara di mana a SalesOrderdapat memiliki status yang berbeda selama masa pakainya. Hanya transisi tertentu yang diizinkan antara negara yang berbeda.

Sekarang OrderStatekelas adalah abstractkelas dan semua metodenya diwarisi ke subclass-nya. Jika kita menganggap subclass Cancelledyang merupakan keadaan akhir yang tidak memungkinkan transisi ke negara lain, kita harus overridesemua metode di kelas ini untuk membuang pengecualian.

Sekarang, bukankah itu melanggar prinsip substitusi Liskov karena seorang sublcass seharusnya tidak mengubah perilaku orang tua? Apakah mengubah kelas abstrak menjadi antarmuka memperbaiki ini?
Bagaimana ini bisa diperbaiki?

Songo
sumber
Ubah OrderState menjadi kelas konkret yang melempar pengecualian "NotSupported" di semua metode secara default?
James
Saya belum membaca buku ini, tetapi aneh bagi saya bahwa OrderState mengandung Cancel () dan kemudian ada status Canceled, yaitu subtipe yang berbeda.
Euforia
@ Euforia mengapa ini aneh? panggilan cancel()mengubah status untuk dibatalkan.
Songo
Bagaimana? Bagaimana Anda bisa memanggil Batal pada OrderState, ketika keadaan negara bagian di SalesOrder seharusnya berubah. Saya pikir seluruh model salah. Saya ingin melihat implementasi penuh.
Euforia

Jawaban:

3

Implementasi khusus ini, ya. Jika Anda membuat kelas konkret daripada implementor abstrak maka Anda akan lolos dari ini.

Namun pola keadaan yang Anda maksudkan yang secara efektif adalah desain mesin negara pada umumnya adalah sesuatu yang tidak saya setujui dari cara saya melihatnya tumbuh. Saya pikir ada cukup alasan untuk mengutuk ini sebagai pelanggaran prinsip tanggung jawab tunggal karena pola manajemen negara ini akhirnya menjadi repositori sentral untuk pengetahuan tentang keadaan terkini dari banyak bagian lain dari suatu sistem. Bagian manajemen negara ini yang tersentralisasi akan lebih sering membutuhkan aturan bisnis yang relevan dengan banyak bagian sistem yang berbeda untuk mengoordinasikannya secara wajar.

Bayangkan jika semua bagian sistem yang peduli tentang negara berada dalam layanan yang berbeda, proses berbeda pada mesin yang berbeda, manajer status pusat yang merinci status untuk masing-masing tempat secara efektif menghambat semua sistem terdistribusi ini dan saya pikir bottleneck adalah tanda pelanggaran SRP serta desain yang umumnya buruk.

Sebaliknya, saya akan menyarankan seseorang membuat objek lebih cerdas seperti pada objek Model dalam pola MVC di mana model tahu bagaimana menangani sendiri, tidak perlu orkestra luar untuk mengelola cara kerjanya atau alasannya.

Bahkan menempatkan pola keadaan seperti ini di dalam objek sehingga hanya mengelola sendiri, rasanya seperti Anda akan membuat objek itu terlalu besar. Alur kerja harus dilakukan melalui komposisi berbagai objek yang bertanggung jawab sendiri, saya katakan, alih-alih dengan satu pengaturan yang mengatur aliran objek lain, atau aliran kecerdasan dalam dirinya sendiri.

Tetapi pada titik itu lebih merupakan seni daripada teknik dan karenanya sangat subyektif pendekatan Anda untuk beberapa hal, yang mengatakan prinsip-prinsipnya adalah panduan yang baik dan ya implementasi yang Anda daftarkan adalah pelanggaran LSP, tetapi bisa diperbaiki untuk tidak dilakukan. Berhati-hatilah dengan SRP saat menggunakan pola apa pun ini dan Anda cenderung aman.

Jimmy Hoffa
sumber
Pada dasarnya, masalah besar adalah bahwa orientasi objek baik untuk menambah kelas baru tetapi membuatnya sulit untuk menambahkan metode baru. Dan dalam kasus negara, seperti yang Anda katakan, tidak mungkin Anda perlu sering memperpanjang kode dengan status baru.
hugomg
3
@Jimmy Hoffa Maaf, tapi apa yang Anda maksud dengan "Jika Anda membuat kelas konkret daripada implementor abstrak maka Anda akan lolos dari ini." ?
Songo
@ Jimmy: Saya mencoba menerapkan status tanpa pola status dengan meminta setiap komponen hanya mengetahui statusnya sendiri. Ini akhirnya membuat perubahan besar untuk mengalir secara signifikan lebih kompleks untuk diterapkan dan dipelihara. Yang mengatakan, saya pikir menggunakan mesin negara (idealnya perpustakaan orang lain untuk memaksanya diperlakukan sebagai kotak hitam) daripada pola desain negara (dengan demikian, negara hanya elemen dalam enum daripada kelas penuh dan transisi hanya bodoh edge) memberikan banyak manfaat dari pola negara sambil memusuhi upaya pengembang yang menyalahgunakannya.
Brian
8

sebuah subkelompok seharusnya tidak mengubah perilaku orang tua?

Itu salah interpretasi umum dari LSP. Subkelas dapat mengubah perilaku induk, asalkan tetap benar untuk tipe induk.

Ada penjelasan panjang yang baik di Wikipedia , menyarankan hal-hal yang akan melanggar LSP:

... ada sejumlah kondisi perilaku yang harus dipenuhi oleh subtipe tersebut. Ini dirinci dalam terminologi yang menyerupai desain dengan metodologi kontrak, yang mengarah ke beberapa batasan tentang bagaimana kontrak dapat berinteraksi dengan warisan:

  • Prasyarat tidak dapat diperkuat dalam subtipe.
  • Postconditions tidak dapat dilemahkan dalam subtipe.
  • Invarian tipe supertipe harus dipertahankan dalam subtipe.
  • Batasan sejarah ("aturan sejarah"). Objek dianggap dapat dimodifikasi hanya melalui metode mereka (enkapsulasi). Karena subtipe dapat memperkenalkan metode yang tidak ada dalam supertipe, pengenalan metode ini memungkinkan perubahan status pada subtipe yang tidak diizinkan dalam supertipe. Batasan sejarah melarang ini. Itu adalah elemen novel yang diperkenalkan oleh Liskov dan Wing. Pelanggaran batasan ini dapat dicontohkan dengan mendefinisikan MutablePoint sebagai subtipe dari ImmutablePoint. Ini merupakan pelanggaran batasan sejarah, karena dalam sejarah titik Immutable, negara selalu sama setelah pembuatan, sehingga tidak bisa menyertakan riwayat MutablePoint secara umum. Namun, bidang yang ditambahkan ke subtipe dapat dimodifikasi dengan aman karena tidak dapat diamati melalui metode supertipe.

Secara pribadi, saya merasa lebih mudah untuk mengingat ini: Jika saya melihat parameter dalam metode yang memiliki tipe A, akankah seseorang yang melewati subtipe B membuat saya terkejut? Jika mereka mau maka ada pelanggaran terhadap LSP.

Apakah melempar Eksepsi merupakan kejutan? Tidak juga. Itu adalah sesuatu yang dapat terjadi kapan saja, apakah saya memanggil metode Kapal di OrderState atau Diberikan atau Dikirimkan. Jadi saya harus menjelaskannya dan itu bukan pelanggaran LSP.

Yang mengatakan , saya pikir ada cara yang lebih baik untuk menangani situasi ini. Jika saya menulis ini dalam C #, saya akan menggunakan antarmuka dan memeriksa implementasi antarmuka sebelum memanggil metode. Misalnya, jika OrderState saat ini tidak mengimplementasikan IShippable, jangan panggil metode Kirim di atasnya.

Tetapi kemudian saya juga tidak akan menggunakan pola Negara untuk situasi khusus ini. Pola keadaan jauh lebih tepat untuk keadaan aplikasi daripada keadaan objek domain seperti ini.

Jadi, singkatnya, ini adalah contoh yang buruk dari Pola Negara dan bukan cara yang sangat baik untuk menangani keadaan suatu pesanan. Tapi itu bisa dibilang tidak melanggar LSP. Dan pola Negara, dalam dan dari dirinya sendiri, tentu saja tidak.

pdr
sumber
Kedengarannya bagi saya seperti Anda membingungkan LSP dengan Prinsip paling tidak mengejutkan / heran, saya percaya bahwa LSP memiliki batas yang lebih jelas di mana ini melanggar mereka, meskipun Anda menerapkan POLA yang lebih subyektif yang didasarkan pada harapan yang subjektif ; yaitu setiap orang mengharapkan sesuatu yang berbeda dari yang berikutnya. LSP didasarkan pada jaminan kontraktual dan terdefinisi dengan baik yang diberikan oleh penyedia, bukan ekspektasi yang dinilai secara subyektif yang ditebak oleh konsumen.
Jimmy Hoffa
@JimmyHoffa: Anda benar, dan itulah sebabnya saya menguraikan arti sebenarnya sebelum saya menyatakan cara yang lebih sederhana untuk mengingat LSP. Namun, jika ada pertanyaan di benak saya apa arti "kejutan" maka saya akan kembali dan memeriksa aturan yang tepat dari LSP (dapatkah Anda benar-benar mengingatnya sesuka hati?). Pengecualian yang menggantikan fungsi (atau sebaliknya) adalah yang sulit karena tidak melanggar klausa tertentu di atas, yang mungkin merupakan petunjuk bahwa itu harus ditangani dengan cara yang sama sekali berbeda.
pdr
1
Pengecualian adalah kondisi yang diharapkan yang didefinisikan dalam kontrak dalam pikiranku, pikirkan NetworkSocket, mungkin pengecualian yang diharapkan pada pengiriman jika tidak terbuka, jika implementasi Anda tidak membuang pengecualian untuk soket tertutup yang Anda langgar LSP , jika kontrak menyatakan yang sebaliknya dan subtipe Anda melempar pengecualian maka Anda melanggar LSP. Itu kurang lebih bagaimana saya mengklasifikasikan pengecualian dalam LSP. Adapun mengingat aturan; Saya bisa salah tentang hal ini dan merasa bebas untuk mengatakannya kepada saya, tetapi pemahaman saya tentang LSP vs POLA didefinisikan dalam kalimat terakhir dari komentar saya di atas.
Jimmy Hoffa
1
@JimmyHoffa: Saya tidak pernah menganggap POLA dan LSP bahkan terhubung dari jarak jauh (saya pikir POLA dalam istilah UI) tetapi, sekarang setelah Anda menunjukkannya, mereka seperti itu. Saya tidak yakin mereka dalam konflik, atau yang satu lebih subjektif daripada yang lain. Bukankah LSP merupakan deskripsi awal POLA dalam istilah rekayasa perangkat lunak? Dengan cara yang sama ketika saya merasa frustrasi ketika Ctrl-C tidak Salin (POLA), saya juga menjadi frustrasi ketika ImmutablePoint dapat diubah karena itu sebenarnya subkelas MutablePoint (LSP).
pdr
1
Saya akan mempertimbangkan CircleWithFixedCenterButMutableRadiussebagai kemungkinan pelanggaran LSP jika kontrak konsumen untuk ImmutablePointmenetapkan bahwa jika X.equals(Y), konsumen dapat dengan bebas mengganti X untuk Y, dan sebaliknya, dan dengan demikian harus menahan diri dari menggunakan kelas sedemikian rupa sehingga substitusi tersebut akan menyebabkan masalah. Jenis ini mungkin dapat mendefinisikan secara sah equalssehingga semua contoh akan dibandingkan sebagai berbeda, tetapi perilaku tersebut kemungkinan akan membatasi kegunaannya.
supercat
2

(Ini ditulis dari sudut pandang C #, jadi tidak ada pengecualian yang diperiksa.)

Menurut artikel Wikipedia tentang LSP , salah satu syarat LSP adalah:

Tidak ada pengecualian baru yang harus dilemparkan dengan metode subtipe, kecuali jika pengecualian itu sendiri merupakan subtipe pengecualian yang dilemparkan oleh metode supertipe.

Bagaimana Anda memahaminya? Apa sebenarnya “pengecualian yang dilemparkan oleh metode supertipe” ketika metode supertipe itu abstrak? Saya pikir itu adalah pengecualian yang didokumentasikan sebagai pengecualian yang dapat dilemparkan oleh metode supertype.

Apakah ini berarti bahwa jika OrderState.Ship()didokumentasikan sebagai sesuatu seperti "melempar InvalidOperationExceptionjika operasi ini tidak didukung oleh keadaan saat ini", maka saya pikir desain ini tidak merusak LSP. Di sisi lain, jika metode supertype tidak didokumentasikan dengan cara ini, LSP dilanggar.

Tapi ini tidak berarti ini adalah desain yang baik, Anda tidak boleh menggunakan pengecualian untuk aliran kontrol normal, yang sepertinya sangat dekat. Juga, dalam aplikasi nyata, Anda mungkin ingin tahu apakah suatu operasi dapat dilakukan sebelum Anda mencoba melakukannya, misalnya untuk menonaktifkan tombol "Kirim" di UI.

svick
sumber
Sebenarnya ini adalah poin yang membuat saya berpikir. Jika subkelas melempar pengecualian yang tidak didefinisikan dalam induk maka itu harus merupakan pelanggaran terhadap LSP.
Songo
Saya berpikir bahwa dalam bahasa di mana pengecualian tidak diperiksa, melempar mereka bukan merupakan pelanggaran terhadap LSP. Itu hanya konsep bahasa itu sendiri, bahwa di mana saja kapan saja pengecualian dapat dibuang. Jadi setiap kode klien harus siap untuk itu.
Zapadlo