Согласно 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
- это своего рода абстрактный класс, который имеет реализацию в PaymentGateway
class.
Абстракция построена с помощью 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
Если вам понравилась эта статья и вы считаете ее полезной, поддержите ее 💚