Общий интерфейс
Часть раздела Объектно-ориентированное программирование путешествия по 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(). Это делает ваш код гибким. Позже вы сможете добавлять новые типы вывода, не изменяя существующие функции.
Задание
ЛегкоДавайте создадим систему обработки платежей, в которой разные способы оплаты можно использовать взаимозаменяемо! Вы создадите два совершенно не связанных между собой 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().
Вы получите три входных значения:
- Номер кредитной карты (например,
4532-1234-5678) - ID банковского счёта (например,
ACC-9876) - Сумма платежа (например,
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 для обработки суммы через оба метода оплаты
-- Сначала кредитную карту, затем банковский перевод
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигурПотренируйтесь самостоятельно: Онлайн-компилятор Lua