Thế giới thực - Nguyên tắc thay thế Liskov


14

Bối cảnh: Tôi đang phát triển một khung nhắn tin. Khung này sẽ cho phép:

  • gửi tin nhắn qua xe buýt dịch vụ
  • đăng ký hàng đợi trên xe buýt tin nhắn
  • đăng ký chủ đề trên xe buýt tin nhắn

Chúng tôi hiện đang sử dụng RabbitMQ, nhưng tôi biết rằng chúng tôi sẽ chuyển sang Microsoft Service Bus (trên tiền đề) trong tương lai rất gần.

Tôi dự định tạo một bộ giao diện và triển khai để khi chúng tôi chuyển sang ServiceBus, tôi chỉ cần cung cấp một triển khai mới mà không sửa đổi bất kỳ mã khách hàng nào (ví dụ: nhà xuất bản hoặc người đăng ký).

Vấn đề ở đây là RabbitMQ và ServiceBus không thể dịch trực tiếp. Ví dụ: RabbitMQ dựa trên Trao đổi và Tên chủ đề, trong khi ServiceBus là tất cả về Không gian tên và Hàng đợi. Ngoài ra, không có giao diện chung giữa máy khách ServiceBus và máy khách RabbitMQ (ví dụ: cả hai có thể có kết nối IC, nhưng giao diện khác nhau - không phải từ một không gian tên chung).

Vì vậy, theo quan điểm của tôi, tôi có thể tạo một giao diện như sau:

public interface IMessageReceiver{
  void AddSubscription(ISubscription subscriptionDetails)
}

Do các thuộc tính không thể dịch của hai công nghệ, việc triển khai ServiceBus và RabbitMQ của giao diện trên có các yêu cầu khác nhau. Vì vậy, ẩn ý RabbitMq của tôi về IMessageReceiver có thể giống như thế này:

public void AddSubscription(ISubscription subscriptionDetails){
  if(!subscriptionDetails is RabbitMqSubscriptionDetails){
    // I have a problem!
  }
}

Đối với tôi, dòng trên phá vỡ quy tắc thay thế của Liskov.

Tôi đã cân nhắc việc lật nó xung quanh, để Đăng ký chấp nhận IMessageConnection, nhưng một lần nữa, Đăng ký RabbitMq sẽ yêu cầu các thuộc tính cụ thể của RabbitMQMessageConnection.

Vì vậy, câu hỏi của tôi là:

  • Tôi có đúng rằng điều này phá vỡ LSP?
  • Chúng ta có đồng ý rằng trong một số trường hợp, điều đó là không thể tránh khỏi, hoặc, tôi có đang thiếu thứ gì không?

Hy vọng, điều này là rõ ràng và về chủ đề!


Là thêm một tham số loại cho giao diện là một tùy chọn cho bạn? Về mặt cú pháp Java, một cái gì đó giống như interface TestInterface<T extends ISubscription>sẽ truyền đạt rõ ràng loại nào được chấp nhận và có sự khác biệt giữa các lần triển khai.
Hulk

@ Hulk, tôi không tin như vậy, vì mỗi lần triển khai sẽ yêu cầu triển khai ISubcrip khác nhau.
GinjaNinja

1
Xin lỗi, những gì tôi muốn đề xuất là interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }. Một triển khai sau đó có thể trông giống như public class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }(trong java).
Hulk

Câu trả lời:


11

Có, nó phá vỡ LSP, bởi vì bạn đang thu hẹp phạm vi của lớp con bằng cách giới hạn số lượng giá trị được chấp nhận, bạn đang tăng cường các điều kiện trước. Cha mẹ chỉ định nó chấp nhận ISubscriptionnhưng đứa trẻ thì không.

Cho dù đó là điều không thể tránh khỏi là một điều để thảo luận. Bạn có thể thay đổi hoàn toàn thiết kế của mình để tránh kịch bản này, có thể lật ngược mối quan hệ bằng cách đẩy công cụ vào nhà sản xuất của bạn? Bằng cách đó, bạn thay thế các dịch vụ được khai báo là giao diện chấp nhận cấu trúc dữ liệu và việc triển khai quyết định những gì chúng muốn làm với chúng.

Tùy chọn khác là cho người dùng API biết rõ ràng rằng tình huống của loại phụ không được chấp nhận có thể xảy ra, bằng cách chú thích giao diện với một ngoại lệ có thể bị ném, chẳng hạn như UnsupportedSubscriptionException. Làm như vậy, bạn xác định điều kiện tiên quyết nghiêm ngặt ngay trong khi lập mô hình giao diện ban đầu và được phép làm suy yếu nó bằng cách không ném ngoại lệ nếu loại chính xác mà không ảnh hưởng đến phần còn lại của ứng dụng gây ra lỗi.


Cảm ơn. Tôi không thể nghĩ làm thế nào để 'lật' nó mà không chuyển vấn đề. Ví dụ: Người đăng ký sẽ cần biết IModel cho RabbitMQ và một cái gì đó khác cho ServiceBus để nhận tin nhắn. Tôi nghĩ rằng ngoại lệ chú thích là cách duy nhất về phía trước.
GinjaNinja

2

Có, mã của bạn phá vỡ LSP ở đây. Trong những trường hợp như vậy, tôi sẽ sử dụng Lớp chống tham nhũng từ các mẫu thiết kế DDD. Bạn có thể xem một ví dụ ở đó: http://www.markhneedham.com/blog/2009/07/07/domain-driven-design-anti-corruption-layer/

Ý tưởng là giảm càng nhiều càng tốt sự phụ thuộc vào hệ thống phụ bằng cách cách ly nó một cách rõ ràng, để giảm thiểu tái cấu trúc khi thay đổi nó

Hy vọng nó giúp !

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.