Dunia Nyata - Prinsip Pergantian Liskov

14

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!

GinjaNinja
sumber
Apakah menambahkan parameter tipe ke antarmuka merupakan opsi bagi Anda? Dalam hal sintaksis Java, sesuatu seperti interface TestInterface<T extends ISubscription>dengan jelas akan mengkomunikasikan tipe mana yang diterima, dan bahwa ada perbedaan antara implementasi.
Hulk
@ Hulk, saya tidak percaya begitu, karena setiap implementasi akan memerlukan implementasi berbeda dari berlangganan.
GinjaNinja
1
Maaf, yang ingin saya usulkan adalah interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }. Implementasi kemudian dapat terlihat seperti public class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }(dalam java).
Hulk

Jawaban:

11

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 ISubscriptiontetapi 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.

Andy
sumber
Terima kasih. Saya tidak bisa memikirkan cara 'membalik' tanpa hanya memindahkan masalah. Misalnya, langganan harus mengetahui IModel untuk RabbitMQ dan sesuatu yang lain untuk ServiceBus untuk menerima pesan. Saya pikir pengecualian yang dijelaskan adalah satu-satunya jalan ke depan.
GinjaNinja
2

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!

Julien
sumber