Menu
Coddy logo textTech

Общий интерфейс

Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy. Урок 40 из 70.

Утиная типизация показала нам, что несвязанные классы могут работать вместе, если у них общие имена method. Эта идея приводит к мощной концепции проектирования: общий интерфейс. Когда вы намеренно проектируете несколько классов так, чтобы у них были одинаковые сигнатуры method, вы создаёте взаимозаменяемые компоненты.

Представьте, что вы создаёте систему ведения журнала. Возможно, вы захотите отправлять сообщения в разные места назначения: в file, на экран или даже в сеть. Вместо того чтобы писать отдельный код для каждого случая, вы проектируете все свои output classes так, чтобы они реализовывали один и тот же :write() method:

local LogFile = {}
LogFile.__index = LogFile

function LogFile:new(filename)
    local obj = {filename = filename}
    setmetatable(obj, LogFile)
    return obj
end

function LogFile:write(message)
    print("[FILE:" .. self.filename .. "] " .. message)
end

local Screen = {}
Screen.__index = Screen

function Screen:new()
    local obj = {}
    setmetatable(obj, Screen)
    return obj
end

function Screen:write(message)
    print("[SCREEN] " .. message)
end

Теперь любой код, которому нужно выводить сообщения, может принимать любой из двух типов:

local function logMessage(output, msg)
    output:write(msg)
end

local file = LogFile:new("app.log")
local screen = Screen:new()

logMessage(file, "Starting up")    -- [FILE:app.log] Запуск
logMessage(screen, "Starting up")  -- [SCREEN] Запуск

Функции logMessage не важно и не нужно знать, какой класс она получает. Она ожидает только метод :write(). Это делает ваш код гибким. Позже вы сможете добавлять новые типы вывода, не изменяя существующие функции.

challenge icon

Задание

Легко

Давайте создадим систему обработки платежей, в которой разные способы оплаты можно использовать взаимозаменяемо! Вы создадите два совершенно не связанных между собой payment класса, которые используют общий интерфейс — метод :process(amount), позволяющий любому коду, обрабатывающему платежи, работать с любым из типов, не зная разницы между ними.

Вы организуете код в трёх файлах:

  • CreditCard.lua: payment класс с конструктором :new(cardNumber), который сохраняет номер карты. Его метод :process(amount) должен выводить Charging {amount} to card {cardNumber}.
  • BankTransfer.lua: полностью отдельный payment класс с конструктором :new(accountId). Его метод :process(amount) должен выводить Transferring {amount} from account {accountId}.
  • main.lua: создайте function с именем handlePayment(paymentMethod, amount), которая принимает любой payment object и amount, а затем вызывает на нём :process(amount). Эта function не должна знать, работает ли она с кредитной картой или банковским переводом. Она лишь ожидает, что у object будет метод :process().

Вы получите три входных значения:

  1. Номер кредитной карты (например, 4532-1234-5678)
  2. ID банковского счёта (например, ACC-9876)
  3. Сумма платежа (например, 150)

В основном файле создайте по одному instance каждого типа платежа, используя первые два входных значения. Затем используйте вашу function handlePayment, чтобы обработать заданный amount обоими способами оплаты: сначала кредитной картой, затем банковским переводом.

Например, если входные значения равны 1111-2222-3333, ACC-5555 и 75, output должен быть таким:

Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555

Преимущество этой конструкции в том, что ваша function handlePayment работает с любым object, который реализует :process(). Вы могли бы добавить class PayPal уже завтра, и если у него есть метод :process(amount), он будет работать с вашей существующей function без каких-либо изменений!

Попробуйте сами

-- Импортировать классы платежей
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')

-- Читать входные данные
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())

-- TODO: Создать функцию handlePayment, которая принимает любой объект платежа
-- и сумму, затем вызывает :process(amount) на нём


-- TODO: Создать экземпляры CreditCard и BankTransfer, используя входные данные


-- TODO: Использовать handlePayment для обработки суммы через оба метода оплаты
-- Сначала кредитную карту, затем банковский перевод
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Lua