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

Библиотека для создания классов в Lua

Рекомендуемые сообщения

Предисловие

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

 


Описание моей библиотеки

Здесь особо описывать нечего, поэтому я просто приложил примеры кода( комментарии обязательны к прочтению! )
 

1. Создание класса



local Class = require("class")

-- Класс для точки в 2д пространстве
local Point2d = Class({
    -- Конструктор класса
    __init__ = function(self, x, y)
        self.x, self.y = x, y
    end,
    
    -- Методы класса
    -- Примечание:
    --   аргументы каждого метода класса обязательно
    --   должны начинаться с "self" (ну, либо с "this" или "it", кому как угодно)
    printCoords = function(self)
        print("X = ".. self.x .."; Y = ".. self.y)
    end
})

-- Создание экземпляра класса Point2d
local p1 = Point2d(5, 11)
-- Вызов метода
p1.printCoords() -- Выведет "X = 5; Y = 11"


local p2 = Point2d(p1.x * 2, p1.y * 2)
p2.printCoords() -- Выведет "X = 10; Y = 22"

2. Ты ведь не забыл про наследования?!
Я не очень люблю нагромождать свой код зависимостями, а потом постоянно ломать голову мыслями "А от чего у меня унаследован вот этот класс? От какого именно класса у меня унаследован этот метод? Почему одно наследование перекрывает другое? "

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



local Class = require("class")

-- Класс для точки в 2д пространстве
local Point2d, Point2d_prototype = Class({
    __init__ = function(self, x, y)
        self.x, self.y = x, y
    end,
    printCoords = function(self)
        print("X = ".. self.x .."; Y = ".. self.y)
    end
})


local Player = Class({
    __init__ = function(self, x, y, ...)
        self.x, self.y = x, y
        
        self.otherInfo = ...
    end,
    
    -- Унаследуем метод "printCoords" у класса Point2d
    -- Для этого обратимся к *прототипу* этого метода и получим наследуемый метод по имени
    printCoords = Point2d_prototype.printCoords
})

-- Создание экземпляра класса Point2d
local point = Point2d(5, 11)
-- Вызов метода
point.printCoords() -- Выведет "X = 5; Y = 11"


local player = Player(4, 2, "RccHD")
player.printCoords() -- Выведет "X = 4; Y = 2"

Исходники библиотеки

 

Библиотека получилась совсем небольшой, поэтому вы можете скопировать исходник прямо из этой статьи:
Либо скачать с pastebin ( https://pastebin.com/WVxaHb4Y )
class.lua :


local prepareObj
prepareObj = function(root, obj, proto, mt)
    for k, v in pairs(proto) do
        local t = type(v)
        if t == "table" and k ~= "__proto__" and k ~= "__root__" then
            obj[k] = prepareObj(root, { __proto__ = v, __root__ = root }, v, mt)
        end
    end
    setmetatable(obj, mt)
    return obj
end
local classMt = {
    __index = function(self, key)
        local v = self.__proto__[key]
        if type(v) == "function"
        then return function(...) return v(self.__root__, ...) end
        else return v end
    end
}
return function(proto)
    proto.__new__ = function(...)
        local obj = { __proto__ = proto }
        obj.__root__ = prepareObj(obj, obj, proto, classMt)
        if proto.__init__ ~= nil then proto.__init__(obj.__root__, ...) end
        return obj
    end
    return proto.__new__, proto
end

Для того, чтобы начать использовать классы в своих программах нужно положить файл class.lua в папку рядом со своими программами (или в /usr/lib , но лучше не надо)
После этого импортируйте библиотеку написав в своем коде вот это:


local Class = require("class")

И помните: важно не количество кода, а его качество!

Изменено пользователем RccHD

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

АХ, ДА, СОВСЕМ ЗАБЫЛ

Если вдруг у кого-то возник вопрос "а где private? а где public? а где local private protected?", то вот вам реализация ваших любимых private и public

 
1. public (публичные параметры, которые доступны другим классам) указываются в функции __init__ . То есть в конструкторе класса(все очень просто)

2. private (в моей библиотеке не реализованы области видимости для "приватных" параметров, но зато такие области видимости реализованы в Lua)
Это намек на то, что не нужно усложнять код, а вместо этого стоит использовать уже реализованные области видимости языка Lua
Для этого нужно обернуть код класса в do..end, таким образом создав для этого класса изолированную область видимости
Пример:


local Class = require("class")

local myObject do
    local secretString = "это приватная меременная без всяких заморочек"

    myObject = Class({
        -- класс без конструктора
        publicString = "а это публичная переменная!!!"
    })
end

-- Создание экземпляра класса myObject
local obj = myObject()

print(obj.publicString) -- Выведет "а это публичная переменная!!!"

print(obj.secretString) -- выведет "nil", так как это приватная переменная класса 

3. А любителям всяких там "local private protected friendly" и прочих извращенских протектед-статиков (привет Java и C++) я ничего не могу предложить.  :) 
Так как я не решился реализовывать настолько извращенские вещи, хотя бы потому что я не мазохист и никогда не стану это использовать на практике

Лично мне простых public и private хватает
Надеюсь я не задел ни чьи религиозные чувства!

 

Изменено пользователем RccHD

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Получается, если у класса-родителя двести пятьдесят методов и класс-наследник хочет их всех унаследовать, все двести пятьдесят нужно перечислить в объявлении наследника? А нельзя это как-то автоматизировать? Например, передавать прототип родителя в функцию Class() вторым параметром, чтобы она занималась наследованием методов.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Дааа... Я вот тоже такой фигнёй маялся неделю назад — и вместо того, чтобы завести нормальные классы: с многоуровневым множественным наследованием, красивым синтаксисом, объявлением мета-методов прямо в классе без необходимости плясать с бубном, конструкторами/деструкторами, сеттерами/геттерами, супер-вызовами, геттерами/сеттерами и прочим — у меня классы делались похожим образом: создаватель классов возвращал функцию-конструктор.

 

И довольно быстро упёрся в разные проблемы с этим: например, я хочу, не создавая инстанс класса, создать кастомный класс, чтобы заменить какую-то константу в исходном классе. Надо унаследоваться и заменить. У меня надо было таскать мета-таблицу, у тебя — прототип. А в той вкусной либе, которую я описывал, достаточно вот такого кода:

local CustomClass = newClass(BaseClass)
CustomClass.SOME_CONSTANT = 42

И затем можно юзать кастомный класс с переопределённым атрибутом.

 

По поводу приватных методов: функцию создавать — это абсолютно лишний оверхэд. Scope в луа создаётся блоком do ... end. Например:

local myObject do
  local secret = "abc"

  myObject = Class({
    public = "hey"
  })
end

Ну и буквы как-то великоваты будут. Вроде не настолько плоховидящие тут люди сидят.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
  • Чем вам не понравилось стандартное исполнение классов в lua?
  • Можно ли в runtime узнать является ли объект чьим-либо наследником или нет?
  • Создание приватных полей и методов похоже на один огромный костыль.
  • Почему если вы не понимаете зачем нужны те или иные фишки языка, то человек который их использует -- извращенец? Мне кажется вы никогда и ничего не писали на c++ или java. Основное приимущество слов private, public, protected и тп. это статическая или очень ранняя динамическая проверка наличия ошибок в программе. static нужен для создания общей памяти для всех классов. Также эти маркеры помогают размечать память объекта, но для луа это не актуально.
  • Насчёт извращенцев, когда вам потребуется наследовать не все методы из родительского класса?
  • Знаете что такое singleton? Как его создать используя вашу библиотеку? Как создать несколько singleton'ов в одном пакете?
  • Есть класс MyClass. Нужно создать метод класса, который создаёт новый объект класса MyClass и что-то с ним делает. Как это сделать?
  • Как переопределить операторы +, -, / и тп.?
  • Нельзя ли избавиться от двойной скобки, используя vararg?

    ​local Player = Class({
        ...
    })
  • Какой код сложнее?

    local myObject = (function()
        local secretString = "это приватная меременная без всяких заморочек"
     
        return Class({
            -- класс без конструктора
            publicString = "а это публичная переменная!!!"
        })
    end)()
    

    или

    local MyClass = Class({
        "public", public_field = "abc",
        "private", private_field = "def"
    })
  • Как думаете почему создатели языка lua отказались от классов?

Изменено пользователем Seryoga

Поделиться сообщением


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

 

Хорошая идея. Наверное так и сделаю

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

И довольно быстро упёрся в разные проблемы с этим: например, я хочу, не создавая инстанс класса, создать кастомный класс, чтобы заменить какую-то константу в исходном классе. Надо унаследоваться и заменить. У меня надо было таскать мета-таблицу, у тебя — прототип. А в той вкусной либе, которую я описывал, достаточно вот такого кода:

local CustomClass = newClass(BaseClass)
CustomClass.SOME_CONSTANT = 42

И затем можно юзать кастомный класс с переопределённым атрибутом.

 

А в чем собственно проблема? Вот так это делается:

 

local customClass = function(...)
    local obj = BaseClass(...)
    obj.SOME_CONSTANT = 42
    return obj
end
Изменено пользователем RccHD

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Чем вам не понравилось стандартное исполнение классов в lua?

С чего вы взяли что мне оно не нравится? Оно скорее вам не нравится, так как там нет public static protected friendly

 

Можно ли в runtime узнать является ли объект чьим-либо наследником или нет?

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

 

Создание приватных полей и методов похоже на один огромный костыль.

Чего ж вы так не любите Lua, что стандартные области видимости языка вам кажутся костылями?

 

Почему если вы не понимаете зачем нужны те или иные фишки языка, то человек который их использует -- извращенец? Мне кажется вы никогда и ничего не писали на c++ или java. Основное приимущество слов private, public, protected и тп. это статическая или очень ранняя динамическая проверка наличия ошибок в программе. static нужен для создания общей памяти для всех классов. Также эти маркеры помогают размечать память объекта, но для луа это не актуально.

Те, кто пишут на C++ и Java пусть используют private и protected и т.п. В таком языке как Lua private и protected выглядят просто по-извращенски

 

Насчёт извращенцев, когда вам потребуется наследовать не все методы из родительского класса?

Представьте себе, что есть объекты Rect, Triangle и Line. У всех этих объектов может быть общий метод Rotate(x, y, z, angle), который будет унаследован у Figure_prototype.Rotate

 

Знаете что такое singleton? Как его создать используя вашу библиотеку? Как создать несколько singleton'ов в одном пакете?

 В Lua это не нужно. 

 

Есть класс MyClass. Нужно создать метод класса, который создаёт новый объект класса MyClass и что-то с ним делает. Как это сделать?

 

Если коротко, то вот так

local myClass = Class({
    __init__ = function(self, x) self.x = x end,
    
    strange_method = function(self, y)
        local myObj = self.__new__(y)
        myObj.desc = "Объект типа myClass"
        return myObj
    end
})
Изменено пользователем RccHD

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

[тут была цитата, но её уже исправили]

 

Нельзя ли избавиться от двойной скобки, используя vararg?

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

-- эти строки эквивалентны:
func({"test"})
func {"test"}

-- и эти:
func("test")
func "test"

 

 

В Lua это не нужно.

Ничего себе: "не нужно". Очень даже нужно было мне, например.

Изменено пользователем Fingercomp

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ничего себе: "не нужно". Очень даже нужно было мне, например.

Ну и как вы бы реализовали singleton?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

 

 

В Lua это не нужно. 

 

Предлагаю расширить цитату до:

 

"ООП в Луа не нужно."

 

:P

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Предлагаю расширить цитату до:

 

"ООП в Луа не нужно."

 

:P

Я имел в виду, что реализовывать singleton в Lua не нужно

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Seryoga сказал(а) 23 Июн 2017 - 11:53: Насчёт извращенцев, когда вам потребуется наследовать не все методы из родительского класса?

Представьте себе, что есть объекты Rect, Triangle и Line. У всех этих объектов может быть общий метод Rotate(x, y, z, angle), который будет унаследован у Figure_prototype.Rotate

Это вы рассказываете где нужно наследование, а не о том где не нужно наследовать все методы.

Изменено пользователем Seryoga

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Это вы рассказываете где нужно наследование, а не о том где не нужно наследовать все методы.

каждый понимает по-своему

 

Вы считаете, что нужно все методы наследовать, а я считаю что не обязательно наследовать именно все методы. Лучше выборочно

Изменено пользователем RccHD

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

каждый понимает по-своему

 

Вы считаете, что нужно все методы наследовать, а я считаю что не обязательно наследовать именно все методы. Лучше выборочно

Можно же просто добавить функцию extendsAll и все дела.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

 

  • Чем вам не понравилось стандартное исполнение классов в lua?
  • Можно ли в runtime узнать является ли объект чьим-либо наследником или нет?
  • Создание приватных полей и методов похоже на один огромный костыль.
  • Почему если вы не понимаете зачем нужны те или иные фишки языка, то человек который их использует -- извращенец? Мне кажется вы никогда и ничего не писали на c++ или java. Основное приимущество слов private, public, protected и тп. это статическая или очень ранняя динамическая проверка наличия ошибок в программе. static нужен для создания общей памяти для всех классов. Также эти маркеры помогают размечать память объекта, но для луа это не актуально.
  • Насчёт извращенцев, когда вам потребуется наследовать не все методы из родительского класса?
  • Знаете что такое singleton? Как его создать используя вашу библиотеку? Как создать несколько singleton'ов в одном пакете?
  • Есть класс MyClass. Нужно создать метод класса, который создаёт новый объект класса MyClass и что-то с ним делает. Как это сделать?
  • Как переопределить операторы +, -, / и тп.?
  • Нельзя ли избавиться от двойной скобки, используя vararg?

    ​local Player = Class({
        ...
    })
  • Какой код сложнее?

    local myObject = (function()
        local secretString = "это приватная меременная без всяких заморочек"
     
        return Class({
            -- класс без конструктора
            publicString = "а это публичная переменная!!!"
        })
    end)()
    или

    local MyClass = Class({
        "public", public_field = "abc",
        "private", private_field = "def"
    })
  • Как думаете почему создатели языка lua отказались от классов?

 

 

Public, private, protected это ущербные костыли, необходимые для того, чтобы мы выбирали какие поля и методы наследовать. У RccHD это все решается простой возможностью наследовать только то что мы хотим. Про остальной бред я даже говорить не хочу.

Изменено пользователем eu_tomat
spoiler

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Если я хочу использовать ООП, я использую moonscript, профит.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

И довольно быстро упёрся в разные проблемы с этим: например, я хочу, не создавая инстанс класса, создать кастомный класс, чтобы заменить какую-то константу в исходном классе. Надо унаследоваться и заменить. У меня надо было таскать мета-таблицу, у тебя — прототип. А в той вкусной либе

Та вкусная либа даже слишком вкусная! Притерная   :D. Надо опробовать. А может и нет.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Присоединяйтесь к обсуждению

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

Гость
Ответить в тему...

×   Вы вставили отформатированное содержимое.   Удалить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.


×
×
  • Создать...