Согласно GoF и их замечательной книге «Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования», определение шаблона моста таково:

«Отделите абстракцию от ее реализации, чтобы они могли различаться независимо».

Хорошо, давайте рассмотрим пример проблемы, которую можно решить с помощью этого шаблона.

Допустим, у нас есть класс FinancialDocumentPayoutService, который мы хотим использовать в качестве службы для отправки денег для определенного финансового документа.

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

Наша система должна поддерживать два основных шлюза для платежей: PayPal и Stripe. Итак, мы собираемся реализовать два подкласса для каждого из платежных шлюзов:

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

Все идет нормально. Но наша система растет, и мы хотим добавить особый вид финансового документа - счет-фактуру. В счете-фактуре должна быть добавлена ​​дополнительная сумма НДС.

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

А теперь нам нужно реализовать сервис для каждого шлюза:

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

Структура наших классов выглядит так:

Если мы решим добавить какой-либо другой тип финансового документа, нам придется добавить два дополнительных класса, которые будут реализовывать для него шлюз PayPal и Stripe.

Шаблон «Мост» решает эту проблему, помещая абстракцию и ее реализацию в отдельные иерархии классов. Давайте посмотрим, как это может выглядеть.

Наш FinancialDocumentPayoutService - это своего рода абстрактный класс, который имеет реализацию в PaymentGatewayclass.

Абстракция построена с помощью InvoicePayoutService и ReceiptPayoutService. Реализация построена с использованием PayPal и Stripe.

Абстракция и реализации разделены, поэтому, если мы решим добавить службу выплат для любого другого вида финансового документа, нам не придется реализовывать его поведение для PayPal и Stripe.

Теперь давайте посмотрим, как это можно реализовать:

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

В приведенном выше примере показаны также некоторые дополнительные правила. ReceiptPayoutService имеет метод pay_half, который вызывает метод из родительского класса (FinancialDocumentPayoutService#pay_with_bonus), и этот метод вызывает метод из класса, который является реализацией (PaymentGateway#pay). Любой другой класс абстракции (например, FancyDocumentPayoutService) может вести себя таким же образом, поэтому абстракция в этом случае отделена от ее реализации.

Используйте схему перемычки, когда:

  • вы хотите избежать постоянной привязки абстракции к ее реализации,
  • как абстракции, так и их реализации должны быть расширяемыми путем создания подклассов,
  • изменения в реализации абстракции не должны влиять на клиентов.

Хотите первыми узнавать о новых статьях из этого блога?

Подпишитесь на мою рассылку сейчас! - http://eepurl.com/cVPm_v

Если вам понравилась эта статья и вы считаете ее полезной, поддержите ее 💚