Ktlo 789 Опубликовано: 9 июня, 2015 (изменено) Я видел статью про ООП (Объектно-Ориентированного Программирования), написанную на этом форуме, и вот, что могу сказать, он может быть немного непонятен новичкам в Lua. Я всегда пользовался другим способом создания "классов". Сначала следует разобраться со значениями. Класс - это набор методов (в нашем случае функций), процедур и переменных которые дальше будут наследоваться объектом или другим классом. Объектами называют сущности, обладающие набором свойств и операций над ними. Объект - это производное класса. В Lua классов как таковых нет, но если пофантазировать, то можно представить функцию как класс, а объект как таблицу. Создание простого псевдо-класса 1. И так создаем новую функцию и называем её к примеру class. function class() --Объявление класса --Тело класса end --Конец класса Пока это просто пустая функция. 2.Теперь пропишем в теле класса следующее: function class() --Объявление класса local Me = { } --Создание объекта return Me --Возвращение объекта (производного класса) end --Конец класса Теперь мы создали пустую таблицу и заставили функцию вернуть её. Для создания полноценного объекта нам не хватает заполнить его свойствами и методами. Таблица будущего объекта должна быть локальной. 3.Создадим свойства и методы к нему: function class() --Объявление класса local Me = { --Таблица для методов и переменных ["Var"] = "HelloWorld"; --Публичная переменная } local Secret = "Cake = Lie" --Скрытая переменная function Me.PrintVar() print(Me.Var) end function Me.PrintSecret() print(Secret) end return Me --Возвращение объекта (производного класса) end --Конец класса Мы создали два свойства одно скрытое (Secret), а второе публичное (Me.Var) и два метода один для вывода на экран свойства Me.Var, а второй для вывода свойства Secret. Скрытые свойства обязательно должны быть локальными, а публичные находиться в таблице объекта. Также вы можете создать локальную функцию, которую смогут использовать только методы этого объекта. 5.Вы можете хранить этот класс в отдельном файле и вызывать его с помощью функции require(), таким образом, чувствовать себя программистом на C, где обычно каждый класс находится в отдельном файле . Выглядит это примерно так: local class = require("имя_библиотеки").class -- загрузили класс из другого файла (если его нет в этом) local Object = class() -- Создание объекта на основе класса Object.PrintVar() --Вызов первого метода --> HelloWorld Object.PrintSecret() --Вызов второго метода --> Cake = Lie Object.Var = "BuyWorld" --Изменение значения свойства Var Object.PrintVar() --Вызов первого метода --> BuyWorld Здесь же я вызвал все методы объекта, поменял значение свойства Var и снова вызвал. Попробуйте сами, всё должно работать и с несколькими объектами тоже. Наследование Сначала необходимо познакомиться с самим термином. Наследование - это перенимание всех свойств и методов одного класса другим. Давайте напишем новый класс и назовём его parent. function parent() --Класс предок для будущего класса наследника local Me = { } local private = { ["Secret"] = 42 } Me.Var = "HelloWorld" function Me.PrintVar() print(Me.Var) end return Me end В этом классе есть свойство Var и метод PrintVar(), а также скрытое свойство в таблице private. Теперь пишем класс наследник: function class() local Me = parent() --Перенимаем таблицу объекта из класса parent(), то есть наследуем. return Me end Всё! Поздравляю, вы наследовали класс! Но на самом деле не полностью, т. к. остаётся наше скрытое свойство. Я не просто так засунул его в таблицу. Мы можем передать эту таблицу как и сам объект, но отдельно. function parent()--Класс предок local Me = { ["Var"] = "HelloWorld" --Публичное свойство } local private = { ["Secret"] = 42 --Скрытое свойство } function Me.PrintVar() --Метод print(Me.Var) end return Me, private --Возвращение объекта, а затем таблицы со скрытыми переменными end function class() --Класс наследник local Me, private = parent() --Наследование function Me.getVar() --Функция, возвращающая свойство Var из класса parent return Me.Var end function Me.getSecret() return private.Secret --Функция, возвращающая скрытое свойство Secret класса parent end return Me, private --Этот класс можно наследовать тоже, таким образом, получится двойное наследование end Теперь тестим: local class = require("filename").class --Нужно, если вы разместили класс в другом файле --[[ Если вы разместили классы в разных файлах, то следует загружать каждый отдельно, но так как они у меня в одном файле, я вызову только наследника, и это всё равно будет работать. ]] local object = class() --Создаём объект object.PrintVar() --Тестим метод из класса предка --> HelloWorld print(object.getVar()) --Тестим метод класса class, работающий с Var --> HelloWorld print(Me.getSecret()) --Получаем значение скрытого свойства --> 42 Как видите, всё должно работать. Вы можете сами попробовать построить этот класс и вызывать его методы. В принципе тут больше нечего объяснять, я думаю. Если что-то не понятно, спрашивайте, отвечу. Экономия оперативной памяти Представим класс: function class() return { ["foo"] = function() --Здесь очень много кода, вычислений, инструкций и т.д. end; ["var"] = "какая-то переменная, для примера" } --Оформил тело объекта так, для разнообразия end Главный минус таких "классов" в том, что они тратят оперативную память, бессмысленно создавая идентичные функции, которые сохраняются отдельно. Но этого можно избежать почти полностью. Почему почти, вы сейчас уведите. Представим класс в несколько ином виде, как блок do <тело класса> end. do --Начало класса local function class_foo(self) --Наша "тяжёлая" функция --Здесь очень много кода, вычислений, инструкций и т.д. end function class() --Функция для вызова класса return { ["foo"] = function() return class_foo(self) --Ссылаемся на локальную функцию end; ["var"] = "какая-то переменная, для примера" } end end --Конец класса Вот и всё! Сложная функция записывается только один раз, а методы ссылаются на неё. Также вы можете не использовать блок do, но тогда функции будут доступны вне класса. Только читаемые свойства Это последнее, о чём стоит написать в этой теме. Только читаемые свойства могут понадобится для возвращения результатов объекта не используя функции, и при этом, не предоставляя возможность редактировать эти самые свойства. Это можно сделать используя магию метатаблиц. Не знаете, что это? Тогда бегом читать! Хотя сейчас можно обойтись и без этого . Объяснить на словах, как это сделать, трудно, так что просто смотрите код: function class() local private = { } --Таблица со скрытыми свойствами private.__index = { --Таблица внутри таблицы скрытых свойств с читаемыми переменными. !!!Важно! Таблица должна сохраняться под ключом __index!!! ReadOnly = true; --Читаемое свойство Set = function( var ) --Читаемый метод, который изменяет значение читаемого свойства private.__index.ReadOnly = var end; } private.secret = 42823 --Скрытое свойство private.__newindex = function() end --Метаметод, непозволяющий менять значение таблицы объекта, если приравняете не к функции, а к таблице будет выводить в неё все попытки замены свойств. local Me = { } --Сам объект, да он пуст setmetatable( Me, private ) --Магия метатаблиц: устанавливает метатаблицу private для объекта return Me, private --Зачем я возвращаю таблицу со скрытыми свойствами, смотрите в наследовании end Надеюсь комментарии к коду выше вполне понятны и всё объясняют. Так что мы можем перейти к тестированию. Тестируем: local class = require 'class'.class --Загрузка класса, если он в другом файле. local object = class() --Получаем объект на основе нашего класса object.Set( "test" ) --Устанавливаем значение для читаемого свойства читаемой функцией. Напрямую мы это сделать не сможем. print(object.ReadOnly) --Выводим значение ReadOnly --> test object.ReadOnly = "He he!" --Изменяем значение, надеясь, что это сработает print(object.ReadOnly) --Но он выводит старое значение --> test На этом всё. Теперь вы можете использовать читаемые свойства для того, чтобы не создавать специальные для этого методы. Вывод. Этот способ реализации ООП в Lua будет работать и в OC и в СС. "Классы" очень похожи на классы из других языков программирования. В моём варианте нет двоеточия, которое приносит странную возможность обработки одного объекта методом другого. Использовать ООП в игре можно для удобной разметки интерфейса в вашей программе (например для создания кнопок), и для экономии системных ресурсов в вашем компьютере внутри компьютера. Изменено 23 июня, 2015 пользователем Ktlo 6 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Lgmrszd 390 Опубликовано: 9 июня, 2015 Обычно натыкаюсь на реализацию классов в виде таблиц. Теперь наконец-то смогу избавиться от двоеточия при вызове функций. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
artem211 428 Опубликовано: 9 июня, 2015 (изменено) И зачем так сложно? Не проще либу стандартную написать? и вызывать также require("liba") local API = {} function API.hello() print("Hello!") end API.var1 = 12345324 API.var2 = true API.var3 = "aasdp" ... ... ... return API Изменено 9 июня, 2015 пользователем artem211 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
LeshaInc 625 Опубликовано: 9 июня, 2015 И зачем так сложно? Не проще либу стандартную написать? и вызывать также require("liba")local API = {} function API.hello() print("Hello!") end API.var1 = 12345324 API.var2 = true API.var3 = "aasdp" ... ... ... return API Понты, понты. 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Krutoy 1 169 Опубликовано: 10 июня, 2015 Ну вот, другое дело. Вот это уже "правильный" "ооп" на луа. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Zer0Galaxy 2 187 Опубликовано: 10 июня, 2015 (изменено) И зачем так сложно? Не проще либу стандартную написать? и вызывать также require("liba") local API = {} function API.hello() print("Hello!") end API.var1 = 12345324 API.var2 = true API.var3 = "aasdp" ... ... ... return API В твоем случае либа всегда будет возвращать указатель на один и тот же объект Obj1=require("liba") Obj2=require("liba") Obj1.var1=54321 print(Obj2.var1) -->54321 т.е. при изменении поля одного объекта изменятся соответствующие поля других объектов этого же класса. В случае же, который предложил Ktlo, создаются новые переменные для полей нового объекта и новые функции для методов. Я видел статью про ООП (Объектно-Ориентированного Программирования), написанную на этом форуме, и вот, что могу сказать, он может быть немного непонятен новичкам в Lua. Я всегда пользовался другим способом создания "классов". Я тоже начинал рассмотрение ООП в Луа именно с такого способа, но мне не понравилось в нем то, что для каждого экземпляра объекта создаются свои экземпляры функций-методов. Т.е сколько ты объектов создашь, столько идентичных функций будет храниться в памяти компьютера. В случае реализации ООП через двоеточие, все объекты одного типа и их наследники используют один и тот же экземпляр функции. С нетерпением жду описания наследования. PS: далось вам это двоеточие. Вы гоняетесь за внешней похожестью, а ООП на самом деле это не синтаксис компилятора, а уровень мышления программиста. Изменено 10 июня, 2015 пользователем Zer0Galaxy Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
LeshaInc 625 Опубликовано: 10 июня, 2015 94НН03 С006Щ3НN3 П0К4ЗЫ8437, К4КN3 У9N8N73ЛЬНЫ3 83ЩN М0Ж37 93Л47Ь Н4Ш Р4ЗУМ! 8П3Ч47ЛЯЮЩN3 83ЩN! СН4Ч4Л4 Э70 6ЫЛ0 7РУ9Н0, Н0 С3ЙЧ4С Н4 Э70Й С7Р0К3 84Ш Р4ЗУМ ЧN7437 Э70 4870М47NЧ3СКN, Н3 З49УМЫ84ЯСЬ 06 Э70М. Г0Р9NСЬ. ЛNШЬ 0ПР393Л3ННЫ3 ЛЮ9N М0ГУ7 ПР0ЧN747Ь Э70. Любой программист это прочитает. 4 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
SergOmarov 34 Опубликовано: 15 июня, 2015 В твоем случае либа всегда будет возвращать указатель на один и тот же объект Obj1=require("liba") Obj2=require("liba") Obj1.var1=54321 print(Obj2.var1) -->54321 т.е. при изменении поля одного объекта изменятся соответствующие поля других объектов этого же класса. В случае же, который предложил Ktlo, создаются новые переменные для полей нового объекта и новые функции для методов. Я тоже начинал рассмотрение ООП в Луа именно с такого способа, но мне не понравилось в нем то, что для каждого экземпляра объекта создаются свои экземпляры функций-методов. Т.е сколько ты объектов создашь, столько идентичных функций будет храниться в памяти компьютера. В случае реализации ООП через двоеточие, все объекты одного типа и их наследники используют один и тот же экземпляр функции. С нетерпением жду описания наследования. PS: далось вам это двоеточие. Вы гоняетесь за внешней похожестью, а ООП на самом деле это не синтаксис компилятора, а уровень мышления программиста. а почему бы не вызвать функцию прямо из класса? или после инициализции удалять объект импортированной функции... Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Zer0Galaxy 2 187 Опубликовано: 15 июня, 2015 а почему бы не вызвать функцию прямо из класса? или после инициализции удалять объект импортированной функции... Приведи пример, пожалуйста. А то не понятно какую функцию из какого класса? Зачем удалять какой объект? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
SergOmarov 34 Опубликовано: 16 июня, 2015 (изменено) Приведи пример, пожалуйста. А то не понятно какую функцию из какого класса? Зачем удалять какой объект? local class = require("имя_библиотеки").class Эту переменную надо очистить после создания всех экземпляров класса Либо, дергать так: require("имя_библиотеки").class() Изменено 16 июня, 2015 пользователем SergOmarov Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Zer0Galaxy 2 187 Опубликовано: 16 июня, 2015 Нет. Ты не путай функцию библиотеки и функцию класса. local class = require("имя_библиотеки").class В данном случае переменная class будет содержать указатель на уже существующую функцию и занимать в памяти аж восемь байт. Но это, к сожалению, не имеет никакого отношения к ООП поскольку класс это не тоже самое, что и библиотека. Способ же, который упомянул топикстартер, действительно создает объект. Но для каждого экземпляра объекта такой способ создает новые экземпляры функций-методов, а функции могут занимать в памяти килобайты. 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
LeshaInc 625 Опубликовано: 16 июня, 2015 (изменено) Если нужно наследование, можно сделать простейший класс, но с двоеточием (не знаю что вы его пугаетесь (?)): local class = {a,b} -- классом может послужить обычная таблица function class:method(a,b) -- это метод класса, например, пусть меняет значения self.a = a. -- self указывает на нашу таблицу "class", это синтаксический сахар из-за двоеточия. self.b = b end function class:new() -- этим методом мы будем создавать новый объект. Вот тут и начинается магия наследования. local answ = {} -- таблица которую в конце функции мы возвратим. self.__index = self -- теперь при обращении к методом класса будут обращаться именно к его методам. (Сам не до конца понял) setmetatable(answ, self) -- магия, указываем метатаблицу answ к классу class return answ -- возвращаем результат. endСдалось вам это двоеточие. Изменено 16 июня, 2015 пользователем LeshaInc Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Zer0Galaxy 2 187 Опубликовано: 16 июня, 2015 Если нужно наследование, можно сделать простейший класс, но с двоеточием (не знаю что вы его пугаетесь (?)): Хм. Тема называется "ДРУГОЙ способ реализации ...". То о чем пишешь ты это, надо полагать, способ первый. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
SergOmarov 34 Опубликовано: 16 июня, 2015 (изменено) Нет. Ты не путай функцию библиотеки и функцию класса. local class = require("имя_библиотеки").class В данном случае переменная class будет содержать указатель на уже существующую функцию и занимать в памяти аж восемь байт. Но это, к сожалению, не имеет никакого отношения к ООП поскольку класс это не тоже самое, что и библиотека. Способ же, который упомянул топикстартер, действительно создает объект. Но для каждого экземпляра объекта такой способ создает новые экземпляры функций-методов, а функции могут занимать в памяти килобайты. А, вот ты о чем, а я не понял) Да, на счет этого ты прав, но ведь функции можно хранить и внешним образом, просто вне объекта Me. А ссылаться так как показано в "сложном способе" ооп. Изменено 16 июня, 2015 пользователем SergOmarov Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Ktlo Автор темы 789 Опубликовано: 22 июня, 2015 Всё я закончил с ООП, если есть вопросы, спрашивайте. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Zer0Galaxy 2 187 Опубликовано: 23 июня, 2015 В моём варианте нет двоеточия, которое приносит странную возможность обработки одного объекта методом другого. Это как? Так что ли? Obj1:Metod1() --Вызов метода объекта 1 (нормальный) Obj2:Metod2() --Вызов метода объекта 2 (нормальный) Obj1.Metod1(Obj2) --Вызов метода объекта 1 для объекта 2 (странный) Был несколько разочарован реализацией readonly-полей. Я надеялся на: print(Obj.ReadOnlyVar) -- читаем поле --> 123 Obj.ReadOnlyVar=456 -- пытаемся изменить print(Obj.ReadOnlyVar) -- повторно читаем поле --> 123 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Ktlo Автор темы 789 Опубликовано: 23 июня, 2015 Это как? Так что ли? Obj1:Metod1() --Вызов метода объекта 1 (нормальный) Obj2:Metod2() --Вызов метода объекта 2 (нормальный) Obj1.Metod1(Obj2) --Вызов метода объекта 1 для объекта 2 (странный) Да, так. Был несколько разочарован реализацией readonly-полей. Я надеялся на: print(Obj.ReadOnlyVar) -- читаем поле --> 123 Obj.ReadOnlyVar=456 -- пытаемся изменить print(Obj.ReadOnlyVar) -- повторно читаем поле --> 123 Тоже бы хотелось так, но это не возможно, так как нельзя запретить добавление значения в таблицу объекта. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
SergOmarov 34 Опубликовано: 23 июня, 2015 Можно) переопределить оператор присваивания Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Ktlo Автор темы 789 Опубликовано: 23 июня, 2015 Можно) переопределить оператор присваивания Как? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
LeshaInc 625 Опубликовано: 23 июня, 2015 Метатаблицы Карл... Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах