Latar Belakang: Saya sedang mengembangkan kerangka kerja pengiriman pesan. Kerangka kerja ini akan memungkinkan:
- pengiriman pesan melalui bus layanan
- berlangganan antrian di bus pesan
- berlangganan topik pada bus pesan
Kami saat ini menggunakan RabbitMQ, tapi saya tahu kami akan pindah ke Microsoft Service Bus (on Premise) dalam waktu dekat.
Saya berencana untuk membuat satu set antarmuka dan implementasi sehingga ketika kita pindah ke ServiceBus, saya hanya perlu memberikan implementasi baru tanpa mengubah salah satu kode klien (yaitu penerbit atau pelanggan).
Masalahnya di sini adalah bahwa RabbitMQ dan ServiceBus tidak langsung diterjemahkan. Misalnya, RabbitMQ bergantung pada Pertukaran dan Nama Topik, sedangkan ServiceBus adalah semua tentang Ruang nama dan Antrian. Juga, tidak ada antarmuka umum antara klien ServiceBus dan klien RabbitMQ (misalnya keduanya mungkin memiliki IConnection, tetapi antarmuka berbeda - bukan dari namespace yang umum).
Jadi menurut saya, saya bisa membuat antarmuka sebagai berikut:
public interface IMessageReceiver{
void AddSubscription(ISubscription subscriptionDetails)
}
Karena sifat-sifat yang tidak dapat diterjemahkan dari kedua teknologi tersebut, implementasi ServiceBus dan RabbitMQ dari antarmuka di atas memiliki persyaratan yang berbeda. Jadi implemetasi RabbitMq saya dari IMessageReceiver mungkin terlihat seperti ini:
public void AddSubscription(ISubscription subscriptionDetails){
if(!subscriptionDetails is RabbitMqSubscriptionDetails){
// I have a problem!
}
}
Bagi saya, baris di atas melanggar aturan substitusi Liskov.
Saya mempertimbangkan untuk membalik ini, sehingga Langganan menerima IMessageConnection, tetapi sekali lagi Langganan RabbitMq akan membutuhkan properti spesifik dari RabbitMQMessageConnection.
Jadi, pertanyaan saya adalah:
- Apakah saya benar bahwa ini memecah LSP?
- Apakah kita setuju bahwa dalam beberapa kasus itu tidak dapat dihindari, atau, apakah saya kehilangan sesuatu?
Semoga ini jelas dan sesuai topik!
sumber
interface TestInterface<T extends ISubscription>
dengan jelas akan mengkomunikasikan tipe mana yang diterima, dan bahwa ada perbedaan antara implementasi.interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }
. Implementasi kemudian dapat terlihat sepertipublic class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }
(dalam java).Jawaban:
Ya, itu melanggar LSP, karena Anda mempersempit ruang lingkup sub-kelas dengan membatasi jumlah nilai yang diterima, Anda memperkuat pra-kondisi. Orang tua menentukan itu menerima
ISubscription
tetapi anak tidak.Apakah itu tidak dapat dihindari adalah hal untuk diskusi. Bisakah Anda benar-benar mengubah desain Anda untuk menghindari skenario ini, mungkin membalik hubungan dengan mendorong barang ke produsen Anda? Dengan begitu Anda mengganti layanan yang dinyatakan sebagai antarmuka yang menerima struktur data dan implementasinya memutuskan apa yang ingin mereka lakukan dengannya.
Opsi lain adalah untuk memberi tahu pengguna API secara eksplisit, bahwa situasi suatu sub tipe yang tidak dapat diterima mungkin terjadi, dengan membuat anotasi antarmuka dengan pengecualian yang mungkin dilemparkan, seperti
UnsupportedSubscriptionException
. Melakukan itu Anda mendefinisikan hak prasyarat ketat selama pemodelan antarmuka awal dan diizinkan untuk melemahkannya dengan tidak membuang pengecualian jika jenisnya benar tanpa mempengaruhi sisa aplikasi yang bertanggung jawab atas kesalahan tersebut.sumber
Ya, kode Anda memecah LSP di sini. Dalam kasus seperti itu, saya akan menggunakan Lapisan Anti Korupsi dari Pola Desain DDD. Anda dapat melihat satu contoh di sana: http://www.markhneedham.com/blog/2009/07/07/domain-driven-design-anti-corruption-layer/
Idenya adalah untuk mengurangi sebanyak mungkin ketergantungan pada sub-sistem dengan mengisolasi secara eksplisit, untuk meminimalkan refactoring ketika mengubahnya
Semoga ini bisa membantu!
sumber