Отношение "Has-A"
Часть раздела Object Oriented Programming путешествия по Lua на Coddy — урок 49 из 70.
На протяжении этого курса вы использовали наследование для совместного использования поведения между классами — Dog «является» Animal. Но наследование не всегда является подходящим инструментом.
Иногда объекты не имеют общего дерева наследования; вместо этого один объект просто содержит другой. Это называется композицией, и она моделирует отношение «имеет» (has-a).
Рассмотрим автомобиль. Автомобиль не является типом двигателя — у него есть двигатель. Двигатель — это отдельный объект, который находится внутри автомобиля:
local Engine = {}
Engine.__index = Engine
function Engine:new(horsepower)
local obj = {horsepower = horsepower}
setmetatable(obj, Engine)
return obj
end
local Car = {}
Car.__index = Car
function Car:new(model, engine)
local obj = {
model = model,
engine = engine -- Сохраняем объект Engine внутри Car
}
setmetatable(obj, Car)
return obj
endПри создании автомобиля вы передаете существующий объект двигателя в его конструктор:
local myEngine = Engine:new(200)
local myCar = Car:new("Sedan", myEngine)
print(myCar.engine.horsepower) -- 200Автомобиль не наследуется от двигателя — он просто хранит ссылку на него. Это делает оба класса независимыми. Вы могли бы заменить двигатель на другой или использовать тот же класс двигателя в мотоцикле.
Композиция дает вам гибкость, которую не может дать наследование: вы собираете объекты из частей, а не запираете их в жесткую иерархию семейств.
Задание
ЛегкоДавайте создадим компьютерную систему, используя композицию! Вместо наследования вы создадите класс Computer, который содержит внутри себя CPU — демонстрируя отношение "has-a" (имеет).
Вы организуете свой код в трех файлах:
CPU.lua: Создайте класс CPU с конструктором:new(brand, speed), который сохраняет название бренда и скорость в ГГц. Добавьте метод:getInfo(), который возвращает строку в формате:{brand} @ {speed}GHzComputer.lua: Создайте класс Computer, чей конструктор:new(model, cpu)принимает название модели и объект CPU. Computer сохраняет объект CPU внутри себя (композиция!). Добавьте метод:describe(), который выводит модель компьютера и информацию о его CPU.main.lua: Соберите все вместе! Считайте входные данные для бренда CPU, скорости CPU и модели компьютера. Сначала создайте объект CPU, затем передайте его в конструктор Computer. Наконец, вызовите метод компьютера:describe().
Вы получите три входных значения:
- Бренд CPU (строка)
- Скорость CPU в ГГц (число)
- Название модели компьютера (строка)
Ваш вывод должен показывать описание компьютера, которое включает информацию из содержащегося в нем CPU:
Model: {model}
CPU: {brand} @ {speed}GHzНапример, если входные данные — Intel, 3.5 и WorkStation, вывод должен быть следующим:
Model: WorkStation
CPU: Intel @ 3.5GHzОбратите внимание, что Computer не наследуется от CPU — он просто хранит ссылку на него. Метод :describe() класса Computer обращается к данным CPU через сохраненный объект, показывая, как композиция позволяет создавать сложные объекты из более простых частей!
Шпаргалка
Объекты могут быть связаны через композицию, которая моделирует отношение «имеет» (has-a). Вместо наследования один объект содержит другой в качестве компонента.
Пример композиции с автомобилем, у которого есть двигатель:
local Engine = {}
Engine.__index = Engine
function Engine:new(horsepower)
local obj = {horsepower = horsepower}
setmetatable(obj, Engine)
return obj
end
local Car = {}
Car.__index = Car
function Car:new(model, engine)
local obj = {
model = model,
engine = engine -- Store the Engine object inside Car
}
setmetatable(obj, Car)
return obj
endСоздание и использование составных объектов:
local myEngine = Engine:new(200)
local myCar = Car:new("Sedan", myEngine)
print(myCar.engine.horsepower) -- 200Композиция обеспечивает гибкость за счет сборки объектов из частей, а не использования жестких иерархий наследования. Компоненты остаются независимыми и могут быть повторно использованы в разных классах.
Попробуйте сами
-- main.lua
-- Объединяем всё вместе, используя композицию
local CPU = require('CPU')
local Computer = require('Computer')
-- Чтение входных данных
local cpuBrand = io.read()
local cpuSpeed = tonumber(io.read())
local computerModel = io.read()
-- TODO: Создайте объект CPU с брендом и скоростью
-- TODO: Создайте объект Computer с моделью и объектом CPU
-- TODO: Вызовите метод describe() компьютера, чтобы вывести результат
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур9Композиция и примеси
Отношение "Has-A"ДелегированиеПростые примесиПрименение нескольких примесейПримеси против наследованияИтоги — Сборка робота