Класс на метатаблицах


Источник: https://habr.com/post/346892/

--
-- Простая реализация класса
--

function Class()
	local class = {}

	-- Метатаблица для класса.
	local mClass = {__index = class}

	-- Функция, которая возвращает "объект" данного класса.
	function class.instance()
		return setmetatable({}, mClass)
	end
	return class
end


-- Пока без двоеточий.
Rectangle = Class()

function Rectangle.new(x, y, w, h)
	local self = Rectangle.instance()
        self.x = x or 0
        self.y = y or 0
	self.w = w or 10
	self.h = h or 10
	return self
end


function Rectangle.area(self)
	return self.w * self.h
end

-- Инстанцируем
rect = Rectangle.new(10, 20, 30, 40)

print('rect.area(rect)', rect.area(rect)) --> 1200
print('rect:area()',     rect:area())     --> 1200




--
-- Реализация класса с наследованием
--

function Class(parent)
	local class = {}
	local mClass = {}

	-- Сам класс будет метатаблицей для объектов.
	-- Это позволит дописывать ему метаметоды.
	class.__index = class

	-- Поля объектов будут искаться по цепочке __index,
	-- и дотянутся, в том числе, до родительского класса.
	mClass.__index = parent

	-- Резервируем поле Super под родителя.
	class.Super    = parent


	-- Функция, которая будет вызываться при вызове класса
	function mClass:__call(...)
		local instance = setmetatable({}, class)

		-- Резервируем поле класса "init"
		if type(class.init) == 'function' then
			-- Возвращаем экземпляр и всё что он вернул функцией init
			return instance, instance:init(...)
		end

		-- Но если её нет - тоже ничего.
		return instance
	end

	return setmetatable(class, mClass)
end

-- Основной класс.
Shape = Class()

function Shape:init(x, y)
	-- в качестве self мы сюда передаём инстанс объекта.
	self.x = x or 0
	self.y = y or 0

	return '!!!'
end

function Shape:posArea()
	return self.x * self.y
end

-- Так как таблица Shape является метатаблицей для рождаемых ей объектов,
-- мы можем дописывать ей метаметоды.
function Shape:__tostring()
	return '[' .. self.x .. ', ' .. self.y ..']'
end

local foo, value = Shape(10, 20)

print('foo, value', foo, value) --> [10, 20] !!!

-- Наследник
Rectangle = Class(Shape)

function Rectangle:init(x, y, w, h)
	-- В данный момент, self - это пустая таблица,
	-- к которой прицеплена таблица Rectangle, как мета.

	-- Вызов родительских методов через Super.
	self.Super.init(self, x, y)
	-- Вызов собственных методов при инициализации - тоже возможен.
	self.w, self.h = self:getDefault(w, h)
end

function Rectangle:area()
	return self.w * self.h
end

function Rectangle:getDefault(w, h)
	return w or 10, h or 20
end

function Rectangle:__tostring()
	return '['..self.x..', '..self.y..', '..self.w .. ', '..self.h..']'
end

rect = Rectangle(10, 20, 30, 40)

print('rect',           rect)           --> [10, 20, 30, 40]

print('rect:area()'   , rect:area())    --> 30 * 40 = 1200

-- Вызов родительского метода
print('rect:posArea()', rect:posArea()) --> 10 * 20 = 200