Katakanlah saya memiliki antarmuka FooInterface
yang memiliki tanda tangan berikut:
interface FooInterface {
public function doSomething(SomethingInterface something);
}
Dan kelas konkret ConcreteFoo
yang mengimplementasikan antarmuka itu:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
}
}
Saya ingin ConcreteFoo::doSomething()
melakukan sesuatu yang unik jika melewati jenis SomethingInterface
objek 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 SpecialSomething
sementara memberikan cadangan untuk SomethingInterface
objek generik ? Sesuatu seperti:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
if (something instanceof SpecialSomething) {
// Do SpecialSomething magic
}
else {
// Do generic SomethingInterface magic
}
}
}
doSomething()
metode ini adalah tipe konversi keSpecialSomething
: jika menerimanyaSpecialSomething
hanya akan mengembalikan objek yang tidak dimodifikasi, sedangkan jika menerimaSomethingInterface
objek generik akan menjalankan algoritma untuk mengubahnya menjadiSpecialSomething
objek. Karena prasyarat dan postkondisi tetap sama, saya tidak percaya kontrak telah dilanggar.y = doSomething(x)
,x.setFoo(3)
lalu menemukan yangy.getFoo()
mengembalikan 3?SpecialSomething
objek sebagai gantinya. Meskipun demi kemurnian, saya juga bisa melihat tidak adanya optimasi kasus khusus ketika objek berlaluSpecialSomething
dan hanya menjalankannya melalui algoritma konversi yang lebih besar karena masih harus bekerja karena itu juga menjadiSomethingInterface
objek.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
SomethingInterface
perlu 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.
sumber
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 denganRandomAccessIterator
s.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.
sumber
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.
sumber
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 .
sumber