Katakanlah kita memiliki daftar entitas Tugas, dan ProjectTask
sub tipe. Tugas dapat ditutup kapan saja, kecuali ProjectTasks
yang tidak dapat ditutup setelah status Mulai. UI harus memastikan opsi untuk menutup permulaan ProjectTask
tidak pernah tersedia, tetapi beberapa perlindungan ada dalam domain:
public class Task
{
public Status Status { get; set; }
public virtual void Close()
{
Status = Status.Closed;
}
}
public class ProjectTask : Task
{
public override void Close()
{
if (Status == Status.Started)
throw new Exception("Cannot close a started Project Task");
base.Close();
}
}
Sekarang ketika memanggil Close()
suatu Tugas, ada kemungkinan panggilan akan gagal jika itu adalah ProjectTask
dengan status yang dimulai, ketika itu tidak akan jika itu adalah Tugas dasar. Tetapi ini adalah persyaratan bisnis. Itu harus gagal. Bisakah ini dianggap sebagai pelanggaran prinsip substitusi Liskov ?
design
object-oriented-design
solid
liskov-substitution
Paul T Davies
sumber
sumber
public Status Status { get; private set; }
:; jika tidak,Close()
metode ini dapat diselesaikan.Task
tidak memperkenalkan ketidakcocokan yang aneh dalam kode polimorfik yang hanya diketahuiTask
adalah masalah besar. LSP bukan kemauan, tetapi diperkenalkan justru untuk membantu pemeliharaan dalam sistem besar.TaskCloser
proses yangclosesAllTasks(tasks)
. Proses ini jelas tidak berusaha menangkap pengecualian; setelah semua, itu bukan bagian dari kontrak eksplisitTask.Close()
. Sekarang Anda memperkenalkanProjectTask
dan tiba-tiba AndaTaskCloser
mulai melempar (mungkin tidak ditangani) pengecualian. Ini masalah besar!Jawaban:
Ya, ini merupakan pelanggaran terhadap LSP. Prinsip Pergantian Liskov mensyaratkan itu
Contoh Anda melanggar persyaratan pertama dengan memperkuat prasyarat untuk memanggil
Close()
metode.Anda dapat memperbaikinya dengan membawa pra-kondisi yang diperkuat ke tingkat atas hierarki warisan:
Dengan menetapkan bahwa panggilan
Close()
valid hanya di negara bagian ketika AndaCanClose()
kembali,true
Anda membuat pra-kondisi berlaku untukTask
dan juga untukProjectTask
, memperbaiki pelanggaran LSP:sumber
Close
melakukan pengecekan, dan menambahkan yang dilindungiDoClose
akan menjadi alternatif yang valid. Namun, saya ingin tetap sedekat mungkin dengan contoh OP; memperbaiki itu adalah pertanyaan terpisah.Iya. Ini melanggar LSP.
Saran saya adalah menambahkan
CanClose
metode / properti ke tugas dasar, sehingga tugas apa pun dapat mengetahui apakah tugas dalam kondisi ini dapat ditutup. Itu juga bisa memberikan alasan mengapa. Dan menghapus virtual dariClose
.Berdasarkan komentar saya:
sumber
Prinsip substitusi Liskov menyatakan bahwa kelas dasar harus dapat diganti dengan sub-kelasnya tanpa mengubah sifat yang diinginkan dari program. Karena hanya
ProjectTask
menimbulkan pengecualian ketika ditutup, program harus diubah menjadi acommodate untuk itu, harusProjectTask
digunakan sebagai penggantiTask
. Jadi itu pelanggaran.Tetapi jika Anda memodifikasi
Task
pernyataan dalam tanda tangannya bahwa hal itu dapat menimbulkan pengecualian saat ditutup, maka Anda tidak akan melanggar prinsip.sumber
Pelanggaran LSP membutuhkan tiga pihak. Tipe T, Subtipe S, dan program P yang menggunakan T tetapi diberi instance S.
Pertanyaan Anda telah memberikan T (Tugas) dan S (ProjectTask), tetapi tidak P. Jadi pertanyaan Anda tidak lengkap dan jawabannya memenuhi syarat: Jika ada P yang tidak mengharapkan pengecualian, maka untuk P itu, Anda memiliki LSP pelanggaran. Jika setiap P mengharapkan pengecualian maka tidak ada pelanggaran LSP.
Namun, Anda memang memiliki pelanggaran SRP . Fakta bahwa keadaan tugas dapat diubah, dan kebijakan yang tugas-tugas tertentu di negara-negara tertentu harus tidak diubah ke negara-negara lain, dua tanggung jawab yang sangat berbeda.
Kedua tanggung jawab ini berubah karena alasan yang berbeda dan karenanya harus berada di kelas yang terpisah. Tugas harus menangani fakta sebagai tugas, dan data yang terkait dengan tugas. TaskStatePolicy harus menangani cara transisi tugas dari negara ke negara dalam aplikasi yang diberikan.
sumber
OpenTaskException
(hint, hint) dan setiap P mengharapkan pengecualian lalu apa yang dikatakan tentang kode ke antarmuka, bukan implementasi? Apa yang saya bicarakan? Saya tidak tahu Aku hanya terkejut bahwa aku mengomentari jawaban Bob Unca.Ini mungkin atau mungkin bukan merupakan pelanggaran terhadap LSP.
Serius. Dengarkan aku.
Jika Anda mengikuti LSP, objek bertipe
ProjectTask
harus berperilaku sebagai objek bertipeTask
diharapkan berperilaku.Masalah dengan kode Anda adalah bahwa Anda belum mendokumentasikan bagaimana objek bertipe
Task
diharapkan berperilaku. Anda memiliki kode tertulis, tetapi tidak memiliki kontrak. Saya akan menambahkan kontrak untukTask.Close
. Bergantung pada kontrak yang saya tambahkan, kode untukProjectTask.Close
mengikuti atau tidak mengikuti LSP.Diberikan kontrak berikut untuk Task.Close, kode untuk
ProjectTask.Close
tidak mengikuti LSP:Diberikan kontrak berikut untuk Task.Close, kode untuk
ProjectTask.Close
tidak mengikuti LSP:Metode yang mungkin diganti harus didokumentasikan dalam dua cara:
"Perilaku" mendokumentasikan apa yang bisa diandalkan oleh klien yang tahu objek penerima adalah
Task
, tetapi tidak tahu kelas apa itu contoh langsungnya. Ini juga memberi tahu desainer subclass mana yang menimpa wajar dan mana yang tidak masuk akal."Perilaku default" mendokumentasikan apa yang bisa diandalkan oleh klien yang tahu bahwa objek penerima adalah contoh langsung dari
Task
(yaitu apa yang Anda dapatkan jika Anda gunakannew Task()
. Ini juga memberi tahu perancang subkelas perilaku apa yang akan diwarisi jika mereka tidak mewarisi timpa metode ini.Sekarang hubungan-hubungan berikut harus berlaku:
sumber
Close
tidak melempar. Jadi tanda tangan menyatakan bahwa pengecualian dapat dilemparkan - tidak mengatakan bahwa tidak akan terjadi. Java melakukan pekerjaan yang lebih baik dalam hal ini. Meski begitu, jika Anda menyatakan bahwa suatu metode mungkin menyatakan pengecualian, Anda harus mendokumentasikan keadaan di mana metode itu mungkin (atau akan). Jadi saya berpendapat bahwa untuk memastikan apakah LSP dilanggar, kita perlu dokumentasi di luar tanda tangan.if (false) throw new Exception("cannot start")
ke kelas dasar. Kompiler akan menghapusnya, dan masih berisi kode apa yang dibutuhkan. Btw. kami masih memiliki pelanggaran LSP dengan solusi ini, karena prasyarat masih diperkuat ...Ini bukan pelanggaran Prinsip Pergantian Liskov.
Prinsip Pergantian Liskov mengatakan:
Alasannya, mengapa penerapan subtipe Anda bukan merupakan pelanggaran terhadap Prinsip Pergantian Liskov, cukup sederhana: tidak ada yang dapat dibuktikan tentang apa yang
Task::Close()
sebenarnya dilakukannya. Tentu,ProjectTask::Close()
melempar pengecualian ketikaStatus == Status.Started
, tapi begitu mungkinStatus = Status.Closed
diTask::Close()
.sumber
Ya, itu pelanggaran.
Saya sarankan Anda memiliki hierarki Anda mundur. Jika tidak semua dekat
Task
, makaclose()
tidak termasuk dalamTask
. Mungkin Anda menginginkan sebuah antarmuka,CloseableTask
yang tidakProjectTasks
dapat diimplementasikan oleh semua.sumber
Task
tidak diterapkan sendiriCloseableTask
maka mereka sedang melakukan casting yang tidak aman di suatu tempat untuk meneleponClose()
.Selain menjadi masalah LSP, sepertinya menggunakan pengecualian untuk mengontrol aliran program (saya harus berasumsi bahwa Anda menangkap pengecualian sepele ini di suatu tempat dan melakukan beberapa aliran kustom daripada membiarkannya merusak aplikasi Anda).
Sepertinya ini menjadi tempat yang baik untuk menerapkan pola Negara untuk TaskState dan membiarkan objek negara mengelola transisi yang valid.
sumber
Saya kehilangan di sini hal penting yang terkait dengan LSP dan Desain oleh Kontrak - dalam prasyarat, adalah penelepon yang bertanggung jawab untuk memastikan prasyarat terpenuhi. Kode yang dipanggil, dalam teori DbC, seharusnya tidak memverifikasi prasyarat. Kontrak harus menentukan kapan tugas dapat ditutup (mis. CanClose mengembalikan Benar) dan kemudian kode panggilan harus memastikan prasyarat terpenuhi, sebelum memanggil Tutup ().
sumber
ProjectTask
. Ini adalah kondisi pasca (dikatakan apa yang terjadi setelah metode dipanggil) dan memenuhinya adalah tanggung jawab kode yang disebut.Ya, ini jelas merupakan pelanggaran terhadap LSP.
Beberapa orang berpendapat di sini bahwa membuat eksplisit di kelas dasar bahwa subclass dapat membuang pengecualian akan membuat ini dapat diterima, tetapi saya tidak berpikir itu benar. Tidak peduli apa yang Anda dokumentasikan di kelas dasar atau pada level abstraksi apa Anda memindahkan kode, prasyarat masih akan diperkuat dalam subkelas, karena Anda menambahkan bagian "Tidak dapat menutup tugas proyek yang dimulai" ke dalamnya. Ini bukan sesuatu yang bisa Anda selesaikan dengan solusi, Anda memerlukan model yang berbeda, yang tidak melanggar LSP (atau kita perlu melonggarkan kendala "prasyarat tidak dapat diperkuat").
Anda dapat mencoba pola dekorator jika Anda ingin menghindari pelanggaran LSP dalam kasus ini. Mungkin berhasil, saya tidak tahu.
sumber