Введение

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)
}

PayPalAPI принят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.

results matching ""

    No results matching ""