Bridge pattern trong ruby
Bài đăng này đã không được cập nhật trong 3 năm
Tiếp nối Serices các bài viết về Design Pattern, với bài trước là Facade Pattern, Hôm nay mình sẽ giới thiếu đến mọi người một Design Pattern nữa khá thú vị đó là Bridge design pattern thuộc nhóm Structural Pattern
Mở đầu (Bridge Pattern là gì ?)
Bridge pattern là việc tách tính trừu tượng (abstraction) ra khỏi tính hiện thực (implementation) của nó. Để từ đó cả 2 có thể hoạt động độc lập với nhau. Điều đó có nghĩa là, ban đầu chúng ta xây dựng class xử lý rất là nhiều, bây giờ chúng ta không muốn để những xử lý đó trong class đó nữa. vì thế chúng ta sẽ tạo ra một class khác và move các xử lý đó qua class mới. Và trong class cũ sẽ giữ một class mới này và class mới này sẽ chịu trách nhiệm xử lý thay cho class ban đầu. Tại sao chúng ta phải làm như vậy??? đi sâu hơn vào ví dụ chúng ta sẽ làm rõ hơn.
Ví dụ , Phân tích
Giả sử chúng ta có một FinancialDocumentPayoutService class được sử dụng như một service để chuyển tiền.
class FinancialDocumentPayoutService
attr_accessor :amount
def pay
raise 'must be implemented'
end
end
Và hệ thống của chúng ta sẽ hỗ trợ cho 2 cổng thanh toán đó là Paypal và Stripe vì thể chúng ta sẽ thể hiện 2 subclass cho mỗi cổng thanh toán.
class PaypalFDPayoutService < FinancialDocumentPayoutService
def pay
PayPalApi.charge!(amount)
end
end
class StripeFDPayoutService < FinancialDocumentPayoutService
def pay
StripeApi.new(amount).charge
end
end
Tuy nhiên, Khi hệ thống của chúng ta lớn dần lên, và muốn thêm một kiểu thanh toán đặc biệt nào đó, ở đây giả sử là Invoice thì nó lại có thêm VAT (vâng phát sinh thêm 1 thằng nào đó) thì giờ nó lại như thế này
class InvoicePayoutService < FinancialDocumentPayoutService
attr_accessor :vat
def price
amount + vat
end
end
Và giờ chúng ta lại phải có 2 thể hiện cho mỗi cổng thanh toán:
class PaypalInvoicePayoutService < InvoicePayoutService
def pay
PayPalApi.charge!(amount)
end
end
class StripeInvoicePayoutService < InvoicePayoutService
def pay
StripeApi.new(amount).charge
end
end
Giờ thì cấu trúc của chúng ta sẽ như thế này:
nếu vẫn tiếp tục áp dụng như thế, mỗi khi chúng ta add thêm một kiểu nào đó của FinancialDocument thì chúng ta lại phải thêm 2 thể hiện cho mỗi cổng thanh toán Paypal và Stripe
Vậy thì khi đó thằng Bridge Pattern được sinh ra để giải quyết vấn đề đó. nó tách abstraction và implementation thành 2 phần riêng biệt. trông như thế này:
Bây giờ thì FinancialDocumentPayoutService sẽ là một abstraction và có các implementation trong PaymentGateway class
Abstraction được tạo với InvoicePayoutService và ReceiptPayoutService class Implementation được taọ với Paypal và Stripe class
Abstraction và Implementation được tách biệt, vì thế mỗi khi chúng ta add thêm một kiểu thanh toán chúng ta không phải implement cho Paypal và Stripe nữa. Bây giờ hãy apply chúng:
class FinancialDocumentPayoutService
attr_accessor :amount
def initialize(payment_gateway)
@payment_gateway = payment_gateway
end
def pay
@payment_gateway.pay(price)
end
def pay_with_bonus(bonus)
@payment_gateway.pay(price * (1.0 - bonus))
end
def price
amount
end
end
class InvoicePayoutService < FinancialDocumentPayoutService
attr_accessor :vat
def price
amount + vat
end
end
class ReceiptPayoutService < FinancialDocumentPayoutService
def pay_half
pay_with_bonus(0.5)
end
end
class PaymentGateway
def pay(amount)
raise 'must be implemented'
end
end
class PayPal < PaymentGateway
def pay(amount)
PayPalApi.charge!(amount)
end
end
class Stripe < PaymentGateway
def pay(amount)
StripeApi.new(amount).charge
end
end
Ví dụ trên cho thấy ReceiptPayoutService class có một method là payhalf là một method đang gọi từ một class cha là FinancialDocumentPayoutService#paywithbonus. Và method này đang gọi một method từ một class là một implementation PaymentGateway#pay.
Kết luận (khi nào thì nên sử dụng)
- Khi bạn muốn trách một ràng buộc vĩnh viễn giữa Abstraction và Implementation (chúng ta không thuộc về nhau, chúng ta không là của nhau) =))
- Cả Abstraction và Implementation của chúng nên được mở rộng bằng subsclass.
- Mỗi sự thay đổi của implementation của mỗi abstraction sẽ không làm ảnh hưởng đến những thứ khác.
All rights reserved