Apakah kasus khusus dengan fallback melanggar Prinsip Pergantian Liskov?


20

Katakanlah saya memiliki antarmuka FooInterfaceyang memiliki tanda tangan berikut:

interface FooInterface {
    public function doSomething(SomethingInterface something);
}

Dan kelas konkret ConcreteFooyang mengimplementasikan antarmuka itu:

class ConcreteFoo implements FooInterface {

    public function doSomething(SomethingInterface something) {
    }

}

Saya ingin ConcreteFoo::doSomething()melakukan sesuatu yang unik jika melewati jenis SomethingInterfaceobjek khusus (katakanlah itu disebut SpecialSomething).

Ini jelas merupakan pelanggaran LSP jika saya memperkuat prasyarat metode ini atau melemparkan pengecualian baru, tetapi apakah itu masih merupakan pelanggaran LSP jika saya membuat objek yang khusus SpecialSomethingsementara memberikan cadangan untuk SomethingInterfaceobjek generik ? Sesuatu seperti:

class ConcreteFoo implements FooInterface {

    public function doSomething(SomethingInterface something) {
        if (something instanceof SpecialSomething) {
            // Do SpecialSomething magic
        }
        else {
            // Do generic SomethingInterface magic
        }
    }

}

Jawaban:


19

Ini mungkin merupakan pelanggaran terhadap LSP tergantung pada apa lagi yang ada dalam kontrak untuk doSomethingmetode ini. Tapi itu hampir pasti bau kode bahkan jika itu tidak melanggar LSP.

Misalnya, jika bagian dari kontrak doSomethingadalah bahwa ia akan menelepon something.commitUpdates()setidaknya satu kali sebelum kembali, dan untuk kasus khusus ia memanggil commitSpecialUpdates(), maka itu merupakan pelanggaran terhadap LSP. Bahkan jika SpecialSomething's commitSpecialUpdates()metode secara sadar dirancang untuk melakukan semua hal-hal yang sama seperti commitUpdates(), itu hanya Terlebih Dahulu hacking sekitar pelanggaran LSP, dan persis seperti satu Hackery tidak perlu dilakukan jika satu diikuti LSP secara konsisten. Apakah hal seperti ini berlaku untuk kasus Anda adalah sesuatu yang harus Anda pecahkan dengan memeriksa kontrak Anda untuk metode itu (apakah eksplisit atau implisit).

Alasan ini adalah bau kode adalah karena memeriksa tipe konkret dari salah satu argumen Anda melewatkan titik mendefinisikan antarmuka / jenis abstrak untuk itu di tempat pertama, dan karena pada prinsipnya Anda tidak lagi dapat menjamin metode bahkan bekerja (bayangkan jika seseorang menulis subkelas SpecialSomethingdengan asumsi yang commitUpdates()akan dipanggil). Pertama-tama, cobalah untuk membuat pembaruan khusus ini berfungsi dalam yang sudah adaSomethingInterface; itulah hasil terbaik yang mungkin. Jika Anda benar-benar yakin tidak dapat melakukannya, maka Anda perlu memperbarui antarmuka. Jika Anda tidak mengontrol antarmuka, maka Anda mungkin perlu mempertimbangkan untuk menulis antarmuka Anda sendiri yang melakukan apa yang Anda inginkan. Jika Anda bahkan tidak dapat membuat antarmuka yang berfungsi untuk mereka semua, mungkin Anda harus menghapus antarmuka sepenuhnya dan memiliki beberapa metode mengambil jenis beton yang berbeda, atau mungkin refactor yang lebih besar sedang dalam rangka. Kami harus tahu lebih banyak tentang sihir yang Anda komentari untuk memberi tahu mana yang sesuai.


Terima kasih! Ini membantu. Secara hipotetis, tujuan dari doSomething()metode ini adalah tipe konversi ke SpecialSomething: jika menerimanya SpecialSomethinghanya akan mengembalikan objek yang tidak dimodifikasi, sedangkan jika menerima SomethingInterfaceobjek generik akan menjalankan algoritma untuk mengubahnya menjadi SpecialSomethingobjek. Karena prasyarat dan postkondisi tetap sama, saya tidak percaya kontrak telah dilanggar.
Evan

1
@Evan Oh wow ... itu kasus yang menarik. Itu mungkin benar-benar tidak bermasalah. Satu-satunya hal yang dapat saya pikirkan adalah bahwa jika Anda mengembalikan objek yang sudah ada alih-alih membangun objek baru, mungkin seseorang tergantung pada metode ini mengembalikan objek baru ... tetapi apakah hal semacam itu dapat menghancurkan orang mungkin bergantung pada bahasa. Mungkinkah seseorang menelepon y = doSomething(x), x.setFoo(3)lalu menemukan yang y.getFoo()mengembalikan 3?
Ixrec

Itu bermasalah tergantung pada bahasanya, meskipun itu harus dengan mudah dikerjakan dengan hanya mengembalikan salinan SpecialSomethingobjek sebagai gantinya. Meskipun demi kemurnian, saya juga bisa melihat tidak adanya optimasi kasus khusus ketika objek berlalu SpecialSomethingdan hanya menjalankannya melalui algoritma konversi yang lebih besar karena masih harus bekerja karena itu juga menjadi SomethingInterfaceobjek.
Evan

1

Itu bukan pelanggaran LSP. Namun, itu masih merupakan pelanggaran aturan "jangan lakukan pemeriksaan tipe". Akan lebih baik untuk mendesain kode sedemikian rupa sehingga spesial muncul secara alami; mungkin SomethingInterfaceperlu anggota lain yang bisa melakukan ini, atau mungkin Anda perlu menyuntikkan pabrik abstrak di suatu tempat.

Namun, ini bukan aturan yang keras dan cepat, jadi Anda perlu memutuskan apakah pertukaran itu sepadan. Saat ini Anda memiliki bau kode, dan kemungkinan hambatan untuk peningkatan di masa mendatang. Menyingkirkannya mungkin berarti arsitektur yang jauh lebih berbelit-belit. Tanpa informasi lebih lanjut, saya tidak bisa membedakan mana yang lebih baik.


1

Tidak, mengambil keuntungan dari fakta bahwa argumen yang diberikan tidak hanya menyediakan antarmuka A, tetapi juga A2, tidak melanggar LSP.

Pastikan saja bahwa jalur khusus tidak memiliki pra-kondisi yang lebih kuat (selain dari yang diuji dalam memutuskan untuk mengambilnya), atau pasca-kondisi yang lebih lemah.

Templat C ++ sering melakukannya untuk memberikan kinerja yang lebih baik, misalnya dengan mensyaratkan InputIterator, tetapi memberikan jaminan tambahan jika dipanggil dengan RandomAccessIterators.

Jika Anda harus memutuskan pada saat runtime sebagai gantinya (misalnya menggunakan casting dinamis), waspadalah terhadap keputusan jalur mana yang akan mengambil semua keuntungan potensial Anda atau bahkan lebih.

Mengambil keuntungan dari kasus khusus sering bertentangan dengan KERING (jangan ulangi diri Anda sendiri), karena Anda mungkin harus menduplikasi kode, dan terhadap KISS (Keep it Simple), karena lebih kompleks.


0

Ada pertukaran antara prinsip "jangan lakukan pengecekan tipe" dan "pisahkan antarmuka Anda". Jika banyak kelas menyediakan cara yang bisa dilakukan tetapi mungkin tidak efisien dalam melakukan beberapa tugas, dan beberapa dari mereka juga dapat menawarkan cara yang lebih baik, dan ada kebutuhan untuk kode yang dapat menerima kategori barang yang lebih luas yang dapat melakukan tugas tersebut (mungkin tidak efisien) tetapi kemudian melakukan tugas seefisien mungkin, akan perlu untuk memiliki semua objek mengimplementasikan antarmuka yang mencakup anggota untuk mengatakan apakah metode yang lebih efisien didukung dan yang lain untuk menggunakannya jika itu, atau yang lain memiliki kode yang menerima objek memeriksa apakah mendukung antarmuka yang diperluas dan melemparkannya jika demikian.

Secara pribadi, saya mendukung pendekatan sebelumnya, meskipun saya berharap kerangka kerja berorientasi objek seperti .NET akan memungkinkan antarmuka untuk menentukan metode default (membuat antarmuka yang lebih besar tidak terlalu menyakitkan untuk digunakan). Jika antarmuka umum mencakup metode opsional, maka satu kelas pembungkus dapat menangani objek dengan berbagai kombinasi kemampuan sambil menjanjikan bagi konsumen hanya kemampuan yang ada dalam objek yang dibungkus asli. Jika banyak fungsi dipecah menjadi antarmuka yang berbeda, maka objek pembungkus yang berbeda akan diperlukan untuk setiap kombinasi antarmuka yang berbeda yang mungkin perlu didukung oleh objek yang dibungkus.


0

Prinsip substitusi Liskov adalah tentang subtipe yang bertindak sesuai dengan kontrak dari supertype mereka. Jadi, seperti yang ditulis Ixrec, tidak ada cukup informasi untuk menjawab apakah ini merupakan pelanggaran LSP.

Apa yang dilanggar di sini adalah prinsip Terbuka tertutup. Jika Anda memiliki persyaratan baru - Lakukan sihir SpecialSomething - dan Anda harus memodifikasi kode yang ada, maka Anda pasti melanggar OCP .

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.