Введение
The Adapter Pattern
отвечает за адаптацию двух несовместимых интерфейсов.Это структурная схема, которая отвечает за объединение функциональных возможностей независимых или несовместимых интерфейсов без изменения их реализации.
Интерфейсы могут быть несовместимы, но внутренняя функциональность должна соответствовать потребностям.Он позволяет другим несовместимым объектам работать вместе, преобразуя интерфейс каждой структуры в интерфейс, ожидаемый клиентами.
Цель
- Импеданс соответствует старому компоненту новой системы
- Оберните интерфейс объекта в другой интерфейс, который ожидают клиенты.
- Адаптер позволяет объектам работать вместе, что в противном случае не могло бы быть связано с несовместимыми интерфейсами.
Схема проектирования
Структуры / объекты, участвующие в шаблоне адаптера, показаны на следующей диаграмме:
Target
это интерфейс, специфичный для домена, который Клиент хочет использовать.Adapter
адаптирует интерфейсAdaptee
кTarget
интерфейсу. Он реализуетTarget
интерфейс с точки зрения Adaptee.Adaptee
определяет существующий интерфейс, который нуждается в адаптации.Client
взаимодействует с объектами, соответствующимиTarget
интерфейсу.
Target
Интерфейс позволяет Объекты типов адаптируемых быть взаимозаменяемыми с любыми другими объектами ,которые могут реализовать один итот же интерфейс.Однако адаптеры могут не соответствоватьTarget
.Один интерфейс не является достаточно мощным механизмом.Нам нужен шаблон адаптера.Adaptee
Предлагает аналогичные функциональные возможности клиента, но под другим именем и ,возможно ,с различными параметрами.ОнAdaptee
полностью независим от других классов и не обращает внимания на любые соглашения об именах или подписи, которые у них есть.
Реализация
Давайте рассмотрим, как мы должны использовать шаблон дизайна адаптера, чтобы принять две несовместимые платежные системы и сделать их доступными для наших клиентов.Предположим, что мы строим систему, которая должна поддерживатьPayPal
и осуществлятьBank
платежи.Кроме того, мы потребляем две внешние библиотеки, которые обрабатывают каждый из этих способов оплаты.
package paypal
import (
"errors"
"fmt"
"regexp"
)
var mailRegexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
// Money of PayPal transactions
type Money struct {
// Amount
Amount float64
// Currency for that amount
Currency string
}
// Payment in PayPal
type Payment struct {
// APIKey is the PayPal API key
APIKey string
}
// Send money
func (*Payment) Send(senderEmail, recipientEmail string, money *Money) error {
if !mailRegexp.MatchString(senderEmail) {
return errors.New("Invalid sender email address")
}
if !mailRegexp.MatchString(recipientEmail) {
return errors.New("Invalid recipient email address")
}
if money == nil {
return errors.New("The money must be provided")
}
if money.Amount <= 0 {
return errors.New("The amount cannot be negative")
}
if money.Currency == "" {
return errors.New("The currency must be provided")
}
fmt.Printf("Send %f %s from %s to %s", money.Amount, money.Currency, senderEmail, recipientEmail)
return nil
}
package bank
import (
"errors"
"fmt"
"time"
)
// AccountType determines the type of bank account
type AccountType uint8
const (
// AccountTypeCurrent is a current bank account
AccountTypeCurrent AccountType = iota
// AccountTypeSaving is a saving bank account
AccountTypeSaving
)
// Account is a bank account
type Account struct {
// Owner is the bank account owner
Owner string
// Email of the owner
Email string
// Balance is the bank account balance
Balance float64
// Currency of the account
Currency string
}
// Transaction is the bank transaction
type Transaction struct {
FromAccount *Account
ToAccount *Account
Amount float64
Date time.Time
Reason string
}
// Gateway for the Bank
type Gateway struct {
// Token Key
Token string
// Accounts
Accounts []*Account
}
// FindAccountByEmail finds a bank account
func (g *Gateway) FindAccountByEmail(email string) (*Account, error) {
for _, account := range g.Accounts {
if account.Email == email {
return account, nil
}
}
return nil, errors.New("Account Not Found")
}
// ProcessTransaction processes a bank transaction
func (g *Gateway) ProcessTransaction(t *Transaction) error {
if t.FromAccount == nil {
return errors.New("FromAccount is missing")
}
if t.ToAccount == nil {
return errors.New("ToAccount is missing")
}
if t.Reason == "" {
return errors.New("Reason is not provided")
}
if t.Amount <= 0 {
return errors.New("Invalid amount")
}
if t.Amount > t.FromAccount.Balance {
return errors.New("Insufficient funds")
}
fmt.Printf("Transfered %f %s from %s to %s at %v", t.Amount,
t.FromAccount.Currency, t.FromAccount.Owner, t.ToAccount.Owner, t.Date)
t.FromAccount.Balance -= t.Amount
return nil
}
Мы разрабатываем карту покупок, которая должна работать с различными способами оплаты:
// Checkouter checkouts order
type Payment interface {
// Pay from email to email this amount
Pay(fromEmail, toEmail string, amount float64) error
}
// Item in the shopping card
type Item struct {
// Name of the item
Name string
// Price of the item
Price float64
}
// ShoppingCard in online store
type ShoppingCard struct {
// Items im the ShoppingCard
Items []*Item
// PaymentMethod selected
PaymentMethod Payment
// ShopEmailAddress address of the shop
ShopEmailAddress string
}
// Checkout checkouts a shopping card
func (c *ShoppingCard) Checkout(payeeEmail string) error {
var total float64
for _, item := range c.Items {
total += item.Price
}
return c.PaymentMethod.Pay(payeeEmail, c.ShopEmailAddress, total)
}
Как вы можете видеть, API Bank API и PayPal API нельзя использовать в качестве разных вариантов оплаты вShoppingCard
объекте из-за их разных подписей.
Чтобы их принять, мы должны реализовать адаптеры, которые подчиняютсяPayment
интерфейсу.
BankAdapter
Адаптирует банк пакет API, обернувbank.Gateway
структуры:
// BankAdapter adapts bank API
type BankAdapter struct {
// Gateway of the bank
Gateway *bank.Gateway
}
// Pay from email to email this amount
func (b *BankAdapter) Pay(fromEmail, toEmail string, amount float64) error {
fromAccount, err := b.Gateway.FindAccountByEmail(fromEmail)
if err != nil {
return err
}
toAccount, err := b.Gateway.FindAccountByEmail(toEmail)
if err != nil {
return err
}
t := &bank.Transaction{
FromAccount: fromAccount,
ToAccount: toAccount,
Amount: amount,
Date: time.Now(),
Reason: "Payment to Online Store",
}
return b.Gateway.ProcessTransaction(t)
}
PayPal
API принятPayPalAdapter
структурой:
// PayPalAdapter adapts PayPal API
type PayPalAdapter struct {
Payment *paypal.Payment
}
// Pay from email to email this amount
func (p *PayPalAdapter) Pay(fromEmail, toEmail string, amount float64) error {
return p.Payment.Send(fromEmail, toEmail, &paypal.Money{Amount: amount, Currency: "USD"})
}
Выводы
The Adapter Pattern
используется везде, где есть код, который должен быть обернут и перенаправлен на другую реализацию.
Но как много нужно
Adapter
делать?
ЕслиTarget
иAdaptee
имеет сходства, то адаптер должен просто делегировать запросы от Target к Adaptee.ЕслиTarget
иAdaptee
не аналогичны, то адаптеру, возможно, придется преобразовать структуры данных между ними и реализовать операции, требуемые Target, но не реализованные Adaptee.