Перейти к основному содержимому

Объектно-ориентированное программирование в Lua

Разработчику Архитектору
Сначала — общие понятия (раздел 4 "Код")

Если ООП для вас новое, сначала пройдите материалы без привязки к синтаксису: парадигмы и уровни абстракции, затем ООП — о разделезачем объекты, введение, абстракция, инкапсуляция, наследование, полиморфизм.

Ниже — ООП через таблицы и метатаблицы в Lua.

Теория и модель Lua

В Lua нет встроенного ключевого слова class. ООП строится на таблицах и метатаблицах (прототипная модель, близкая к JavaScript).

Метатаблица — вторая таблица, привязанная к первой через setmetatable(obj, meta). Она задаёт поведение по умолчанию для обычной таблицы:

  • __index — куда смотреть, если ключа нет в самой таблице (типичное наследование методов класса).
  • __newindex — что делать при записи в отсутствующий ключ (контроль присваивания).
  • __call — что происходит при вызове таблицы как функции (Class() вместо Class:new()).
  • __tostring — строковое представление для print.

Без метатаблицы таблица — просто набор полей. С метатаблицей та же таблица ведёт себя как объект с прототипом.

Понятие ООПКак выражено в Lua / LuauАналог в Java
Объекттаблица с полями и функциямиэкземпляр класса
Класс (шаблон)таблица-прототип + __indexclass
Создание экземпляраClass:new() / setmetatable({}, {__index = Class})new Class()
Конструкторфункция new / init в классеClassName(...)
Методполе-функция; вызов obj:method()метод экземпляра
selfпервый аргумент метода (неявно через :)this
Инкапсуляцияlocal upvalue, замыкания, соглашение _privateprivate
Наследованиецепочка метатаблиц __indexextends
Полиморфизмduck typing: есть метод — объект подходитинтерфейсы + override
Абстракциятаблица-контракт без экземпляра; модуль APIinterface, abstract class
Статикаполя на таблице класса, не на экземпляреstatic
Метапрограммирование__index, __newindex, __callreflection (ограниченно)
Библиотеки ООПMiddleClass, classic, Luau export typeвстроенная модель

В Lua нет ключевого слова class. Объект — таблица с полями и функциями; метод — поле-функция, а : подставляет self. Наследование (в том числе множественное) и сокрытие полей строятся цепочкой метатаблиц и метаметодов __index / __newindex, без отдельного синтаксиса языка. Подход ближе к JavaScript (прототипы), чем к Java/C#, и требует явной настройки.

Определения — раздел 4-08-oop. Синтаксис Lua — о разделе Lua и Luau. Для сравнения с номинальной моделью — ООП в Java.

Кратко для новичка:

  • Таблица {} — единственная структура данных; ею же моделируют объекты и классы.
  • Класс (прототип) — таблица с методами; Class.__index = Class.
  • Экземпляр — новая таблица + setmetatable({}, Class) или Class:new().
  • Методobj:method() эквивалентен obj.method(obj).

Создание экземпляра и присваивание переменным

Таблица как объект

local point = { x = 10, y = 20 }
local alias = point -- та же ссылка на таблицу
alias.x = 99 -- изменится и point.x

local other = { x = 10, y = 20 } -- другая таблица в памяти

Разбор:

  • В Lua все объекты (таблицы, функции, userdata) — ссылочные: присваивание копирует ссылку, не содержимое.
  • Для копии таблицы — ручной перебор, функция clone в библиотеке или сериализация; аналога clone в Java нет в стандартной библиотеке Lua 5.x.

Паттерн Class:new() — конструктор

local Person = {}
Person.__index = Person

function Person:new(name, age)
local obj = setmetatable({}, self) -- self == Person
obj.name = name
obj.age = age or 0
return obj
end

local p = Person:new("Алексей", 30)
local q = p -- та же ссылка
local r = Person:new("Борис")

Разбор:

  • setmetatable({}, self) создаёт пустую таблицу-экземпляр; метатаблица self (класс) с полем __index указывает, где искать методы (Person), если их нет в экземпляре.
  • Person:new(...) — идиома: двоеточие передаёт Person как первый аргумент self.
  • p и q — один объект; r — другой экземпляр с собственными полями.

Отличие . и :

-- Эквивалентны:
person.speak(person, "привет")
person:speak("привет")

Разбор:

  • Точка не подставляет self — при вызове метода через . нужно передать объект вручную.
  • Ошибка attempt to index a nil value часто означает забытый : или отсутствующий self.

Видимость и инкапсуляция (нет private)

В Lua нет модификаторов доступа на уровне языка. Практики:

ПодходМеханизмНадёжность
Соглашениеполе _balance — не трогать снаружинизкая
Локальные upvaluelocal balance внутри фабрики newвысокая
Модульlocal функции в module; наружу — APIвысокая
Luau --!strictexport type без лишних полейпроверка типов
local function newAccount(owner)
local balance = 0 -- недоступен снаружи

local account = {}
function account:deposit(amount)
if amount > 0 then balance = balance + amount end
end
function account:getBalance()
return balance
end
account.owner = owner
return account
end

Разбор:

  • balance живёт в замыкании — снаружи нет ключа для прямой записи.
  • Это ближе к Java private float balance, чем к префиксу _.

Четыре столпа ООП в Lua

Интерактивные схемы — общие принципы (псевдокод). Подробнее: абстракция, инкапсуляция, наследование, полиморфизм.

Play ITЗагрузка интерактивного демо…

Play ITЗагрузка интерактивного демо…

Play ITЗагрузка интерактивного демо…

Play ITЗагрузка интерактивного демо…

Абстракция

Контракт задаётся таблицей с сигнатурами или модулем без конструирования абстрактного экземпляра:

-- Интерфейс — документация + проверка в Luau
export type Notifier = {
send: (self: Notifier, message: string) -> (),
}

local function createConsoleNotifier(): Notifier
return {
send = function(_, message)
print("[LOG] " .. message)
end,
}
end

Разбор:

  • В чистом Lua контракт — соглашение; в Luau export type даёт статическую проверку формы таблицы.
  • Аналог Java interface — таблица с обязательными функциями; абстрактный класс — прототип с методами, вызывающими error("abstract") в базовой реализации.

Инкапсуляция

Скрытие состояния через замыкания и контролируемые методы (раздел Инкапсуляция ниже и embed lua-515-171-014).

Наследование

Цепочка __index: дочерний прототип ссылается на родителя; методы переопределяются полями в дочерней таблице.

Полиморфизм

Любая таблица с методом draw подходит для function render(shape) shape:draw() endутиная типизация без implements.


Объектно-ориентированное программирование в Lua

Интерактивная схема — класс и объект (псевдокод, подходит для любого ООП-языка). Полный разбор принципов: ООП в разделе "Код и разработка".

КЛАСС Кот
поля: имя, возраст
метод мяукнуть()
КОНЕЦ

объект barsik := новый Кот(имя="Барсик", возраст=3)
barsik.мяукнуть()

Разбор:

  • Псевдокод или схема: показывает идею без привязки к синтаксису Lua.
  • В фрагменте 6 строк(и); читайте сверху вниз как последовательность шагов.

Play ITЗагрузка интерактивного демо…


Классы, объекты и элементы классов (свойства и методы)

Объектно-ориентированное программирование (ООП) — это парадигма программирования, которая организует код вокруг объектов, объединяющих данные и поведение. В языке Lua отсутствует встроенная поддержка классов как таковых, однако механизм таблиц и метатаблиц позволяет реализовать полноценную систему ООП, полностью соответствующую принципам объектной ориентации.

Класс — это шаблон или blueprint, определяющий структуру данных и набор функций, доступных для создания объектов. В Lua класс представляет собой обычную таблицу, содержащую поля для хранения свойств и методов. Таблица выступает в роли пространства имен, где хранятся определения всех элементов класса.

Объект — это конкретный экземпляр класса, созданный на основе шаблона. Объект обладает собственным состоянием, которое определяется значениями его свойств, и способностью выполнять действия через свои методы. Каждый объект в Lua является отдельной таблицой, наследующей структуру от родительского класса.

Свойство (поле) — это элемент данных, хранящийся внутри объекта или класса. Свойства описывают состояние объекта и могут иметь различные типы данных — числа, строки, таблицы, функции или другие объекты. В контексте ООП свойства часто называют атрибутами или членами класса.

Метод — это функция, связанная с объектом или классом, которая выполняет определенные действия над данными объекта. Методы определяют поведение системы и позволяют манипулировать состоянием объекта или взаимодействовать с другими частями программы. В Lua методы представляют собой функции, записанные внутри таблицы класса.

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

При обращении к свойству используется символ точки (.). Синтаксис выглядит следующим образом: object.name или class.property. Точка указывает интерпретатору Lua на необходимость получить значение конкретного поля из таблицы. Значение может быть любым типом данных, включая функцию, но при таком обращении функция не получает автоматического доступа к таблице-объекту.

При вызове метода используется символ двоеточия (:). Синтаксис выглядит следующим образом: object:methodName(). Двоеточие выполняет двойную функцию. Во-первых, оно вызывает функцию, записанную в таблице. Во-вторых, оно автоматически передает сам объект в качестве первого аргумента функции под именем self. Этот механизм позволяет методу работать с данными именно того объекта, который был указан перед двоеточием.

Различие между этими операторами критически важно для правильной работы кода. Использование точки вместо двоеточия при вызове метода приведет к ошибке, так как функция ожидает получить объект как первый параметр, но получит только ссылки на себя без контекста. Использование двоеточия при чтении свойства приведет к попытке вызвать функцию, что также вызовет ошибку выполнения.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

В приведенном примере таблица Person выступает в роли класса. Поле species является свойством класса. Функция speak является методом. Объект person1 создается как новая таблица с уникальными свойствами name и age. Механизм setmetatable с полем __index связывает объект с классом, позволяя искать недостающие поля и методы в таблице родителя.

Для свойства используют точку: person1.name — читается поле name в самой таблице (или через __index, если ключа нет).

Для метода — двоеточие: person1:speak() эквивалентно person1.speak(person1). Интерпретатор ищет speak в объекте и в цепочке __index, затем вызывает функцию, передавая person1 как первый аргумент self.

function Person:greet()
-- self ссылается на объект person1
print(self.name .. " говорит: " .. self.age .. " лет")
end

person1:greet() -- Выведет: Алексей говорит: 30 лет

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • Оператор .. склеивает строки (и автоматически приводит числа к строке при конкатенации).
  • Ключевые вызовы в фрагменте: print().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Такой подход обеспечивает гибкость и мощь объектной модели Lua. Объекты могут иметь собственные уникальные свойства, при этом разделяя общую логику поведения с другими экземплярами того же класса. Система метатаблиц скрывает сложность реализации наследования и полиморфизма, предоставляя разработчику удобный синтаксический сахар.


Создание класса и экземпляра

Процесс создания класса в Lua начинается с объявления таблицы, которая будет служить шаблоном для будущих объектов. Эта таблица содержит определения всех полей и методов, которые будут доступны экземплярам. Важно понимать, что сама таблица не является классом в традиционном смысле, пока не будет настроена система метатаблиц для поддержки наследования.

Для создания базового класса достаточно объявить пустую таблицу и добавить в нее функции и переменные.

local MyClass = {}

MyClass.value = 0

function MyClass:init(val)
self.value = val
end

function MyClass:getValue()
return self.value
end

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Таблица MyClass теперь содержит статическое поле value и два метода init и getValue. Однако, чтобы использовать этот шаблон для создания объектов, необходимо создать механизм, который позволит новым таблицам наследовать эти члены.

Создание экземпляра (объекта) происходит путем создания новой таблицы и назначения ей метатаблицы, указывающей на родительский класс. Ключевым механизмом здесь является поле __index в метатаблице. Когда программа пытается обратиться к полю объекта, которого там нет, Lua автоматически смотрит в таблицу, указанную в __index, и продолжает поиск там.

-- Создание экземпляра
local obj1 = setmetatable({}, {__index = MyClass})

-- Инициализация объекта
obj1:init(42)

-- Вызов метода
print(obj1:getValue()) -- Выведет: 42

Разбор:

  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

В данном коде создаётся новая пустая таблица obj1. Ей присваивается метатаблица с полем __index, равным MyClass. При вызове obj1:init(42) интерпретатор ищет метод init в obj1. Не находя его там, он переходит к MyClass и находит функцию. Функция вызывается, и self внутри неё ссылается на obj1.

Конструктор в Lua — это специальная функция, предназначенная для первичной настройки нового объекта. В отличие от строго типизированных языков, где конструктор имеет фиксированное имя, в Lua роль конструктора может играть любая функция, обычно называемая new или create, либо метод init, вызываемый сразу после создания объекта.

Реализация конструктора через метод new является наиболее распространенной практикой. Такой подход инкапсулирует процесс создания и инициализации объекта в одном месте.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • Логика записывается словами and/or/not, а не символами &&/||/! как в C-подобных языках.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

В методе new создается новая таблица instance, которой назначается метатаблица со ссылкой на self (то есть на таблицу Rectangle). Затем происходит инициализация полей width и height. Если аргументы не переданы, используются значения по умолчанию. Функция возвращает готовый объект.

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

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

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

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

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • ipairs обходит последовательную часть таблицы (индексы 1,2,3…); pairs — все пары ключ-значение.
  • Цикл for повторяет тело: в числовой форме перебирает диапазон, в generic — элементы через итератор.
  • Логика записывается словами and/or/not, а не символами &&/||/! как в C-подобных языках.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте — pairs(), print(), setmetatable().

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

local SharedData = {count = 0}

local ObjA = setmetatable({data = SharedData}, {__index = MyParent})
local ObjB = setmetatable({data = SharedData}, {__index = MyParent})

ObjA.Data.count = 10
print(ObjB.Data.count) -- 10, так как обе таблицы ссылаются на один объект SharedData

Разбор:

  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

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

function MyClass:new()
local instance = setmetatable({}, {__index = self})
instance.Data = {} -- Новая таблица для каждого экземпляра
return instance
end

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Система создания классов и объектов в Lua основана на динамической природе таблиц и метатаблиц. Это дает разработчику полный контроль над структурой объектов и позволяет адаптировать модель под любые нужды проекта.


Конструкторы

Конструктор — это специальный метод или функция, ответственная за создание и начальную настройку нового объекта. В языке Lua конструктор не является зарезервированным элементом языка, поэтому разработчик определяет его сигнатуру и поведение самостоятельно. Основная задача конструктора — подготовить объект к использованию, установив начальные значения свойств и выполнив необходимые проверки.

В Lua принято выделять два этапа работы конструктора: выделение структуры объекта и инициализация его состояния. Часто эти этапы объединяются в одну функцию, называемую new. Эта функция принимает параметры, необходимые для создания объекта, создает новую таблицу, настраивает её метатаблицу для наследования и возвращает готовый экземпляр.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • if … then … end выбирает ветку по truthiness: ложными считаются только nil и false.
  • Логика записывается словами and/or/not, а не символами &&/||/! как в C-подобных языках.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Вызовы из стандартных библиотек table — готовые функции языка, не нужно писать их с нуля.
  • type Имя = { … } в Luau объявляет именованный тип для статической проверки.
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.

В примере выше функция BankAccount:new выступает в роли конструктора. Она принимает имя владельца и начальную сумму баланса. Если баланс не указан, устанавливается значение по умолчанию 0. Также создается пустая таблица для истории транзакций. Возвращаемый объект уже полностью готов к работе.

Конструкторы могут содержать логику валидации входных данных. Это позволяет предотвратить создание объектов в недопустимом состоянии. См. Проверка и валидация.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • if … then … end выбирает ветку по truthiness: ложными считаются только nil и false.
  • Логика записывается словами and/or/not, а не символами &&/||/! как в C-подобных языках.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Вызовы из стандартных библиотек string — готовые функции языка, не нужно писать их с нуля.
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте — error(), setmetatable(), string.match().

При попытке создать пользователя с коротким именем или неправильным email возникнет ошибка, что предотвратит появление некорректных данных в системе.

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

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • Оператор .. склеивает строки (и автоматически приводит числа к строке при конкатенации).
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

В этом случае конструктор автоматически инициирует соединение, избавляя разработчика от необходимости вызывать метод подключения отдельно.

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

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • type Имя = { … } в Luau объявляет именованный тип для статической проверки.
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Здесь Car:new создаёт экземпляр через конструктор родителя Vehicle.new(Vehicle, "Car"), затем вешает метатаблицу Car, чтобы методы вроде :refuel искались в цепочке Car → Vehicle.

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

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • ipairs обходит последовательную часть таблицы (индексы 1,2,3…); pairs — все пары ключ-значение.
  • Цикл for повторяет тело: в числовой форме перебирает диапазон, в generic — элементы через итератор.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • type Имя = { … } в Luau объявляет именованный тип для статической проверки.
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте — pairs(), print(), setmetatable().

Такой подход полезен при создании множества похожих объектов с небольшими отличиями.

Важным аспектом работы конструкторов является управление памятью. Поскольку Lua использует сборщик мусора, объекты, созданные в конструкторе, автоматически освобождаются, когда перестают использоваться. Однако, если конструктор открывает внешние ресурсы (файлы, сокеты, соединения БД), их необходимо закрывать в деструкторе или специальном методе очистки.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • if … then … end выбирает ветку по truthiness: ложными считаются только nil и false.
  • Логика записывается словами and/or/not, а не символами &&/||/! как в C-подобных языках.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • io.open открывает файл; режим "r"/"w"/"a" задаёт чтение, перезапись или дозапись.
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: error(), io.open(), setmetatable().

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


Наследование

Интерактивная схема — наследование (псевдокод). Подробнее: Наследование.

Play ITЗагрузка интерактивного демо…

Наследование — это механизм объектно-ориентированного программирования, позволяющий одному классу (дочернему или производному) заимствовать свойства и методы другого класса (родительского или базового). В Lua реализация наследования основана на использовании метатаблиц и ключевого поля __index. Этот механизм обеспечивает динамическое разрешение членов класса, позволяя объектам получать доступ к элементам, определенным в родительской таблице.

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

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • Оператор .. склеивает строки (и автоматически приводит числа к строке при конкатенации).
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

В примере выше таблица Dog наследуется от Animal через setmetatable(Dog, {__index = Animal}). Метод speak переопределяется в классе Dog, что демонстрирует способность дочерних классов изменять поведение родителей. При этом свойство age остается доступным, так как оно определено в родительском классе.

Конструктор дочернего класса вызывает Animal.new(Animal, name), затем задаёт метатаблицу Dog, чтобы экземпляр видел переопределённые методы.

Наследование может быть многоступенчатым, когда класс наследуется от класса, который уже является потомком другого класса.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • Оператор .. склеивает строки (и автоматически приводит числа к строке при конкатенации).
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Цепочка наследования Cat -> Mammal -> Animal позволяет классу Cat использовать функциональность всех предков. Метод speak переопределяется на каждом уровне, обеспечивая специфичное поведение для каждого типа животного.

Динамическая природа Lua позволяет изменять поведение классов во время выполнения. Можно добавлять новые методы в родительский класс, и все наследники автоматически получат доступ к ним.

function Animal:move()
print(self.name .. " движется")
end

myDog:move() -- Рекс движется
myCat:move() -- Барсик движется

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • Оператор .. склеивает строки (и автоматически приводит числа к строке при конкатенации).
  • Ключевые вызовы в фрагменте: print().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Полиморфизм в сочетании с наследованием позволяет писать универсальный код, работающий с объектами разных типов, но имеющими общий интерфейс.

function makeSound(animal)
animal:speak()
end

makeSound(myDog) -- Рекс лает: Гав!
makeSound(myCat) -- Барсик мяукает: Мяу!
makeSound(myDog) -- Рекс лает: Гав!

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Функция makeSound принимает любой объект, у которого есть метод speak, и вызывает его. Благодаря наследованию и полиморфизму, она работает корректно для всех типов животных.

Наследование также поддерживает множественное наследование через составление метатаблиц, хотя стандартный подход в Lua предполагает одноуровневое наследование.

Код ITЗагрузка примера кода…

Разбор:

  • Оператор .. склеивает строки (и автоматически приводит числа к строке при конкатенации).
  • ipairs обходит последовательную часть таблицы (индексы 1,2,3…); pairs — все пары ключ-значение.
  • Цикл for повторяет тело: в числовой форме перебирает диапазон, в generic — элементы через итератор.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте — pairs(), print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

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

Наследование в Lua предоставляет гибкую и мощную систему для построения иерархий классов. Оно позволяет повторно использовать код, расширять функциональность и создавать абстрактные модели, отражающие реальные отношения между объектами.


Инкапсуляция

Интерактивная схема — инкапсуляция (псевдокод). Подробнее: Инкапсуляция.

Play ITЗагрузка интерактивного демо…

Инкапсуляция — это принцип объектно-ориентированного программирования, который ограничивает прямой доступ к некоторым компонентам объекта, скрывая внутреннюю реализацию и предоставляя контролируемый интерфейс для взаимодействия. В Lua инкапсуляция достигается за счет использования локальных переменных, замыканий и правил соглашения об именах, так как язык не имеет жестких модификаторов доступа вроде private или public.

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

В Lua принято считать, что переменные, начинающиеся с подчеркивания (_), являются приватными и не предназначены для прямого доступа извне. Это соглашение помогает разработчикам идентифицировать внутренние данные, которые должны быть скрыты.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • if … then … end выбирает ветку по truthiness: ложными считаются только nil и false.
  • Логика записывается словами and/or/not, а не символами &&/||/! как в C-подобных языках.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.

В этом примере данные счета (balance и owner) хранятся в локальной таблице privateData, которая недоступна извне. Доступ к ним осуществляется только через методы getBalance, setOwner, deposit и withdraw. Эти методы содержат логику проверки, например, запрет на снятие средств, превышающих баланс.

Использование замыканий позволяет создать истинную инкапсуляцию, так как переменные находятся в области видимости функции конструктора и не могут быть изменены напрямую.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • Логика записывается словами and/or/not, а не символами &&/||/! как в C-подобных языках.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Здесь переменная count находится в области видимости функции Counter:new. Даже если кто-то попытается изменить её напрямую, это не удастся, так как она не экспортируется в публичный интерфейс.

Инкапсуляция также позволяет внедрить логику валидации и аудита изменений.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • ipairs обходит последовательную часть таблицы (индексы 1,2,3…); pairs — все пары ключ-значение.
  • Цикл for повторяет тело: в числовой форме перебирает диапазон, в generic — элементы через итератор.
  • if … then … end выбирает ветку по truthiness: ложными считаются только nil и false.
  • Логика записывается словами and/or/not, а не символами &&/||/! как в C-подобных языках.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Вызовы из стандартных библиотек table — готовые функции языка, не нужно писать их с нуля.

В этом примере метод addGrade проверяет допустимость оценки перед добавлением её в список. Метод getAverage вычисляет среднее значение, используя только легальные данные.

Инкапсуляция также полезна для управления состоянием объекта. Например, можно запретить изменение определенных полей после инициализации.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • if … then … end выбирает ветку по truthiness: ложными считаются только nil и false.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: error(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Инкапсуляция в Lua, хотя и не имеет жесткой защиты на уровне языка, эффективно реализуется через архитектурные решения и соглашения. Она обеспечивает безопасность данных, упрощает тестирование и делает код более понятным и поддерживаемым.


Полиморфизм

Интерактивная схема — полиморфизм (псевдокод). Подробнее: Полиморфизм.

Play ITЗагрузка интерактивного демо…

Полиморфизм — это способность объектов разных классов обрабатываться одинаковым образом через общий интерфейс, несмотря на различия во внутренней реализации. В языке Lua полиморфизм реализуется динамически благодаря наличию таблиц и метатаблиц, что позволяет функциям работать с любыми объектами, обладающими необходимыми методами.

В Lua полиморфизм не требует явного объявления интерфейсов или абстрактных классов. Достаточно, чтобы объекты имели методы с одинаковыми именами и сигнатурами. Интерпретатор Lua разрешает вызов этих методов в runtime, опираясь на фактическое наличие метода в объекте.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • Оператор .. склеивает строки (и автоматически приводит числа к строке при конкатенации).
  • ipairs обходит последовательную часть таблицы (индексы 1,2,3…); pairs — все пары ключ-значение.
  • Цикл for повторяет тело: в числовой форме перебирает диапазон, в generic — элементы через итератор.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте — ipairs(), print(), setmetatable().

В этом примере массив shapes содержит объекты разных типов: Circle, Square и базовый Shape. Цикл перебирает все объекты и вызывает метод draw. Несмотря на разные реализации, каждый объект корректно выполняет свою версию метода. Это и есть проявление полиморфизма.

Рисуется круг радиусом 5
Рисуется квадрат со стороной 10
Рисуется фигура

Разбор:

  • Псевдокод или схема: показывает идею без привязки к синтаксису Lua.
  • В фрагменте 3 строк(и); читайте сверху вниз как последовательность шагов.

Полиморфизм позволяет писать универсальный код, который не зависит от конкретных типов объектов. Функция может принимать любой объект, имеющий нужный метод, и выполнять над ним действие.

function renderAll(objects)
for _, obj in ipairs(objects) do
if obj.draw then
obj:draw()
end
end
end

renderAll(shapes)

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • ipairs обходит последовательную часть таблицы (индексы 1,2,3…); pairs — все пары ключ-значение.
  • Цикл for повторяет тело: в числовой форме перебирает диапазон, в generic — элементы через итератор.
  • if … then … end выбирает ветку по truthiness: ложными считаются только nil и false.
  • Ключевые вызовы в фрагменте: ipairs().
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Функция renderAll проверяет наличие метода draw в каждом объекте и вызывает его. Если метод отсутствует, объект пропускается. Это делает код устойчивым к добавлению новых типов фигур без необходимости изменения функции рендеринга.

Динамический полиморфизм в Lua также позволяет заменять поведение объектов во время выполнения.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • Оператор .. склеивает строки (и автоматически приводит числа к строке при конкатенации).
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • Комментарии после -- поясняют ожидаемый результат; при запуске интерпретатор их игнорирует.
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Метод action может быть изменен для любого экземпляра, что демонстрирует гибкость полиморфизма в Lua.

Полиморфизм также проявляется в перегрузке методов через проверку типов аргументов.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • Оператор .. склеивает строки (и автоматически приводит числа к строке при конкатенации).
  • if … then … end выбирает ветку по truthiness: ложными считаются только nil и false.
  • Логика записывается словами and/or/not, а не символами &&/||/! как в C-подобных языках.
  • type(x) возвращает строку с именем типа значения (number, string, table, function и т.д.).
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.

Функция calculate ведет себя по-разному в зависимости от типов переданных аргументов. Это пример ад-hoc полиморфизма, реализованного через проверку типов.

Полиморфизм в Lua упрощает архитектуру программ, позволяя создавать модульные и расширяемые системы. Разработчики могут добавлять новые классы, не затрагивая существующий код, если новые классы соответствуют ожидаемому интерфейсу.

Код ITЗагрузка примера кода…

Разбор:

  • function объявляет функцию; end закрывает тело. Имя после function становится ссылкой на вызываемый объект.
  • Синтаксис obj:method() передаёт obj первым аргументом (self) — удобный стиль для методов таблиц.
  • return завершает функцию и может вернуть несколько значений через запятую.
  • setmetatable связывает таблицу с метатаблицей; поля __… задают перехват операций (индекс, арифметика, вызов).
  • Фигурные скобки {} создают таблицу: можно задать поля key = value и/или элементы-массив с индексами с 1.
  • Ключевые вызовы в фрагменте: print(), setmetatable().
  • При переносе в свой проект сохраните порядок шагов и проверьте результат через print или отладчик.

Функция generate работает с любыми отчётами, имеющими методы start, render и endReport. Добавление нового типа отчета (например, HTML) не потребует изменения кода генератора.

Полиморфизм в Lua — это мощный инструмент, который делает код гибким, расширяемым и легко поддерживаемым. Он позволяет абстрагироваться от конкретных реализаций и сосредоточиться на общих принципах взаимодействия объектов.


Метатаблицы и библиотеки ООП

Ключевые метаметоды для объектной модели

МетаметодКогда вызываетсяТипичное ООП-применение
__indexчтение отсутствующего ключанаследование методов прототипа
__newindexзапись в отсутствующий ключконтроль присваивания, proxy
__callвызов таблицы как функцииClass() как синтаксический сахар
__tostringпреобразование в строкуаналог toString()
__gcсборка мусора (userdata)освобождение ресурсов
local Vector = {}
Vector.__index = Vector

setmetatable(Vector, {
__call = function(_, x, y)
return Vector:new(x, y)
end,
})

function Vector:new(x, y)
return setmetatable({ x = x, y = y }, self)
end

function Vector:__tostring()
return string.format("Vector(%s, %s)", self.x, self.y)
end

local v = Vector(3, 4) -- __call на классе
print(v) -- Vector(3, 4)

Разбор:

  • __call на классе позволяет писать Vector(3, 4) вместо Vector:new(3, 4).
  • __tostring срабатывает при print(v) и tostring(v).

Популярные OOP-библиотеки

БиблиотекаИдеяКогда выбирать
Вручную (__index)минимум зависимостейскрипты, обучение, встраивание
MiddleClassклассы, super, миксиныигры (LÖVE), средние проекты
classicлаконичный синтаксис class:extends()прототипы, малый код
Luau OOPexport type, --!strictRoblox, типобезопасные API

В production на Roblox обычно комбинируют Luau-типы с паттерном Component / модулями, а не глубокие иерархии классов — ближе к композиции, чем к Java-наследованию.

Множественное наследование через __index-цепочку

local function mixin(target, ...)
for i = 1, select("#", ...) do
local source = select(i, ...)
for k, v in pairs(source) do
if type(v) == "function" and target[k] == nil then
target[k] = v
end
end
end
end

Разбор:

  • Lua позволяет собирать поведение из нескольких таблиц — аналог PHP trait, но без встроенного разрешения конфликтов.
  • При коллизии имён нужна явная политика (как insteadof в PHP).

Сравнение Lua с Java

ТемаLua / LuauJava
Модель объектовпрототипы (таблицы + метатаблицы)классы, номинальные типы
Ключевое слово classнет (синтаксический сахар только в библиотеках)есть
Создание экземпляраClass:new() / фабрикаnew Class()
this / selfself первым аргументом; : подставляетthis неявно
Приватностьзамыкания, localprivate в JVM
Наследованиецепочка __indexextends (один класс)
Интерфейсыduck typing; в Luau — export typeinterface
Полиморфизмпо наличию методовпо типу и override
Статическая типизацияLuau optional; чистый Lua — нетвсегда
Сборка мусораtracing GCtracing GC

Java даёт жёсткий контракт на этапе компиляции; Lua — гибкость в runtime ценой дисциплины в команде (соглашения, Luau strict, тесты).


Типичные ошибки

ОшибкаСимптомЧто делать
Вызов метода через . вместо :self nil, поля не находятсявсегда obj:method() для методов
Общая таблица в поле классавсе экземпляры делят один массивсоздавать {} в new, не в прототипе
Забыли setmetatable / __indexметод есть в классе, но не у объекташаблон Class.__index = Class
Псевдоприватное поле _xлюбой код может записатьзамыкание или модуль с local
Глубокая цепочка прототиповсложно отследить, откуда методплоские иерархии, композиция
Копирование таблицы через =неожиданные мутацииявное клонирование
Нет проверки контрактавызов несуществующего метода в runtimeLuau types, assert в фабрике

Чеклист проектирования класса в Lua

  1. Фабрика — один путь создания (new), все поля инициализированы.
  2. ПрототипClass.__index = Class (или библиотека делает это за вас).
  3. Состояние — секреты в local upvalue, не в публичной таблице.
  4. Методы — вызов только через :; сигнатуры документированы.
  5. Наследование — не глубже 2–3 уровней; иначе миксины/композиция.
  6. Контракт — в Luau export type; в чистом Lua — модуль + тесты.
  7. Ресурсыclose() / __gc для файлов и соединений.

Для повторения принципов: ООП — о разделе. Смежные материалы: таблицы в Lua, ООП в Java.


Учебные примеры ООП

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

Класс и объект

Чертёж класса Figure и конкретные объекты — круг и квадрат.

Код ITЗагрузка примера кода…


Банковский счёт

Инкапсуляция: скрытое поле баланса и методы deposit/withdraw.

Код ITЗагрузка примера кода…


Наследование

Родитель Animal и дочерние Cat и Dog с общим eat() и своим speak().

Код ITЗагрузка примера кода…


Смартфон

Состояние объекта: заряд батареи, звонки и подзарядка.

Код ITЗагрузка примера кода…


Студент

Список оценок, средний балл и проходной порог.

Код ITЗагрузка примера кода…


Корзина покупок

Взаимодействие Product, Cart и Order при оформлении заказа.

Код ITЗагрузка примера кода…


Автомобиль

Пробег, расход топлива и напоминание о техобслуживании.

Код ITЗагрузка примера кода…


Пользователь

Скрытый пароль, вход в систему и публикация сообщений.

Код ITЗагрузка примера кода…