Bridge pattern trong ruby

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à PaypalStripe 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 PaypalStripe

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 InvoicePayoutServiceReceiptPayoutService class Implementation được taọ với PaypalStripe class

AbstractionImplementation đượ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 PaypalStripe 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.