Menu
Coddy logo textTech

Отношение "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

Автомобиль не наследуется от двигателя — он просто хранит ссылку на него. Это делает оба класса независимыми. Вы могли бы заменить двигатель на другой или использовать тот же класс двигателя в мотоцикле.

Композиция дает вам гибкость, которую не может дать наследование: вы собираете объекты из частей, а не запираете их в жесткую иерархию семейств.

challenge icon

Задание

Легко

Давайте создадим компьютерную систему, используя композицию! Вместо наследования вы создадите класс Computer, который содержит внутри себя CPU — демонстрируя отношение "has-a" (имеет).

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

  • CPU.lua: Создайте класс CPU с конструктором :new(brand, speed), который сохраняет название бренда и скорость в ГГц. Добавьте метод :getInfo(), который возвращает строку в формате: {brand} @ {speed}GHz
  • Computer.lua: Создайте класс Computer, чей конструктор :new(model, cpu) принимает название модели и объект CPU. Computer сохраняет объект CPU внутри себя (композиция!). Добавьте метод :describe(), который выводит модель компьютера и информацию о его CPU.
  • main.lua: Соберите все вместе! Считайте входные данные для бренда CPU, скорости CPU и модели компьютера. Сначала создайте объект CPU, затем передайте его в конструктор Computer. Наконец, вызовите метод компьютера :describe().

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

  1. Бренд CPU (строка)
  2. Скорость CPU в ГГц (число)
  3. Название модели компьютера (строка)

Ваш вывод должен показывать описание компьютера, которое включает информацию из содержащегося в нем 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() компьютера, чтобы вывести результат
quiz iconПроверьте себя

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

Все уроки раздела Object Oriented Programming