Bridge design pattern in Ruby

According to GoF and their great book “Design Patterns: Elements of Reusable Object-Oriented Software”, bridge pattern definition is:

„Decouple an abstraction from its implementation so that the two can vary independently.”

OK, so let’s check sample problem that can be solved with that pattern.

Let’s say we have a FinancialDocumentPayoutService class, that we want to use as a service to send a money for a specific financial document.

class FinancialDocumentPayoutService
attr_accessor :amount
  def pay
raise 'must be implemented'
end
end

Our system should support two main gateways for payments: PayPal and Stripe. So we gonna implement two subclasses for each of the payment gateways:

class PaypalFDPayoutService < FinancialDocumentPayoutService
def pay
PayPalApi.charge!(amount)
end
end
class StripeFDPayoutService < FinancialDocumentPayoutService
def pay
StripeApi.new(amount).charge
end
end

So far, so good. But our system grows and we want to add special kind of a financial document, which is an invoice. An invoice should have additional VAT amount added.

class InvoicePayoutService < FinancialDocumentPayoutService
attr_accessor :vat
  def price
amount + vat
end
end

And now, we have to implement a service for each gateway:

class PaypalInvoicePayoutService < InvoicePayoutService
def pay
PayPalApi.charge!(amount)
end
end
class StripeInvoicePayoutService < InvoicePayoutService
def pay
StripeApi.new(amount).charge
end
end

Structure of our classes looks like this:

If we decide to add any other type of financial document, we will have to add two additional classes that will implement PayPal and Stripe gateway for it.

The Bridge pattern addresses that problem by putting abstraction and its implementation in separate class hierarchies. Let’s take a look how this could look like.

Our FinancialDocumentPayoutService is kind of a abstract class that has an implementation in PaymentGatewayclass.

Abstraction is build with InvoicePayoutService and ReceiptPayoutService. Implementation is build with PayPal and Stripe .

Abstraction and implementations are separated so if we decide to add payout service for any other kind of financial document, we will not have to implement how it behaves for PayPal and Stripe.

Now, let’s check how this can be implemented:

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

Above example shows also some additional rules. ReceiptPayoutService has a method pay_half that is calling a method from a parent class (FinancialDocumentPayoutService#pay_with_bonus) and that method is calling a method from a class that is an implementation (PaymentGateway#pay). Any other abstraction class (like FancyDocumentPayoutService) can behave the same way, so abstraction in that case is separated from its implementation.

Use the bridge pattern when:

  • you want to avoid a permanent binding between an abstraction and its implementation,
  • both the abstractions and their implementations should be extensible by subclassing,
  • changes in the implementation of an abstraction should have no impact on clients.

Want to know first about new articles from that blog?

Subscribe to my newsletter now! — http://eepurl.com/cVPm_v


If you like this article and consider it useful for you, please support it with 💚

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.