|
Здравствуйте, гость ( Вход | Регистрация )
![]() ![]() |
|
![]() ![]()
Сообщение
#1
|
|
![]() RusHub team lead ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз ![]() |
Объектно-Ориентированное Программирование (ООП) в LUA
В данной теме обсуждаются мощнейшие методы ООП ![]() Итак, потихоньку буду писать основные понятия. Объект и класс Прежде всего следует понять что такое объект, и что такое класс Согласно языку С++, класс - это определяемый пользователем тип, а объект - это экземпляр класса. Попробуем в lua определить понятие класса. Каждому классу могут соответствовать несколько объектов. Каждый объект занимает своё место в памяти и манипулирует со своими переменными и функциями. Приведу пример класса и объектов: Возьмём класс - автомобиль. У этого класса есть некие свои "переменные" и "функции", такие как цвет (функция определения цвета), тип (хэтчбек, универсал, седан) и тд. Объектом данного класса, например, будет красный седан Другим объектом будет синий универсал Третьим объектом будет также синий универсал Второй и третий объекты не отличаются ничем, однако, они разные объекты, физически разные машины, хоть и выглядят одинаково (они занимают разные адреса в памяти). Итак, класс - это некая общность объектов. Конструктор Функция, которая предназначается для инициализации (создания) объектов называется конструктором. Ввиду того, что такая функция создаёт (конструирует) объект. В С++ имя конструктора совпадает с именем класса, и вызывается при выделении памяти под объект. В lua бы будем использовать для конструктора имя: __init, а вызываться конструктор будет при создании объекта. Пример: Код class.MyClass { __init = function(a, b, c) -- конструктор с параметрами a, б и c ... -- описание конструктора end } MyObject = MyClass(1, 2, 3) -- создание объекта (вызов конструктора) То есть при создании объекта обязательно сначала вызывается конструктор. В приведённом примере при создании объекта по средствам конструктора в объект передаются некоторые значения: 1, 2, 3. Если конструктор не содержит передаваемых параметров, то такой конструктор называется конструктором по умолчанию. Деструктор Деструктор вызывается каждый раз при уничтожении объекта. Это полная противоположность конструктору. Закрытые переменные (private переменные) Закрытые переменные - это переменные, которые могут использоваться только членами-классами. В lua закрытые переменные у нас будут содержать 2 подчёркивания спереди. Защищённые переменные (protected переменные) Защищённые переменные - это переменные, которые могут наследоваться В lua защищённые переменные у нас будут содержать 1 подчёркивание спереди. продолжение следует... |
|
|
![]() ![]()
Сообщение
#2
|
|
![]() RusHub team lead ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз ![]() |
ООП широко распространено из-за его эффективности. Эта эффективность выражается в экономном использовании памяти и ресурсов компьютера. Достигается это всё при помощи таких мощных аппаратов как инкапсуляция, наследование и полиморфизм и абстракция.
Реализация класса, которая будет описываться в данном топике является первым вариантом, и служит для введения в курс дела. В своих проектах же не следует её использовать. В проектах используйте доработанную версию библиотеки. Итак, во-первых, не вдаваясь в подробности, напишу основную функцию, которая содержит в себе все выше изложенные аппараты. Те, кто достаточно хорошо знают язык LUA, могут попытаться понять как это всё работает, ну а всем остальным нужно только скопировать без понимания - понимать нужно будет дальше (после этого кода). Код local _G = _G Приведённая функция (cClass) представляет из себя функцию для реализации класса.local function concat(p1, p2) local t = {} for k, v in _G.pairs(p1) do t[k] = v end for k, v in _G.pairs(p2) do t[k] = v end if _G.next(p1.__parent or {}) or _G.next(p2.__parent or {}) then t.__parent = concat(p1.__parent or {}, p2.__parent or {}) end return t end cClass = function(...) local t, m = {}, {} _G.setmetatable(t, m) for i = 1, _G.select('#', ...) do local p = _G.select(i, ...) if _G.type(p) == 'table' then for k, v in _G.pairs(p) do t[k] = v end end end m.__call = function(self, ...) local t, mt_old, mt_new = {}, {}, {} for k, v in _G.pairs(_G.getmetatable(self) or {}) do mt_old[k] = v end for k, v in _G.pairs(self) do mt_old[k] = v end for i = 1, _G.select('#', ...) do local p = _G.select(i, ...) if _G.type(p) == 'table' then for k, v in _G.pairs(_G.getmetatable(p) or {}) do mt_new[k] = v end for k, v in _G.pairs(p) do t[k] = v end end end mt_new = concat(mt_old, mt_new) t.__parent = mt_new mt_new.__index = mt_new _G.setmetatable(t, mt_new) return t end return t end Давайте же попробуем написать свой класс, используя эту самую функцию: Код cMyFirstClass = cClass{ mMember1 = 5; mMember2 = "first_class_mMember2"; func1 = function(self) Core.SendToAll(self.mMember2) end func2 = function(self) Core.SendToAll"Example" end } Функция содержит 2 члена (числовой и строковый) и два метода (func1, func2). Выведем содержимое всех членов класса на экран: Код for k, v in pairs(cMyFirstClass) do Core.SendToAll("["..k.."] = "..tostring(v)) end На экране видим следующее: Цитата [mMember1] = 5 [func1] = function: 01A57710 [func2] = function: 01A525B0 [mMember2] = first_class_mMember2 Наследование. Теперь рассмотрим аппарат наследования. Построим ещё один класс (будем его называть производным классом), который будет наследовать все члены и функции нашего (базового) класса cMyFirstClass. Код cMySecondClass = cMyFirstClass{ mMember2 = "second_class_mMember2"; func3 = function(self) self:func2() end } Если теперь мы выведем на экран все члены класса: Код for k, v in pairs(cMyFirstClass) do то мы увидим следующее:Core.SendToAll("["..k.."] = "..tostring(v)) end Цитата [mMember2] = second_class_mMember2 то есть, мы видим элемент mMember2 и метод func3, но также мы можем наблюдать некую таблицу __parent. Это таблица базового класса (cMyFirstClass).[func3] = function: 01A12170 [__parent] = table: 01A640F0 Полиморфизм. К любым элементам базового класса можно обращаться через эту самую таблицу __parent. Например, я не могу из класса cMySecondClass обратиться непосредственно к элементу mMember2 класса cMyFirstClass, так как в классе cMySecondClass есть элемент с точно таким же именем и при обращении именно он будет возвращаться: Код Core.SendToAll(cMySecondClass.mMember2) на экране видимЦитата second_class_mMember2 Однако, если написать так:Код Core.SendToAll(cMySecondClass.__parent.mMember2) мы увидим на экране желаемый результат:Цитата first_class_mMember2 Инкапсуляция. Теперь рассмотрим аппарат инкапсуляции. Код local cMyFirstClass = {} do local mMember3 = "private_mMember3" cMyFirstClass = cClass{ mMember1 = 5; mMember2 = "first_class_mMember2"; func1 = function(self) Core.SendToAll(self.mMember2) end func2 = function(self) Core.SendToAll"Example" end } end Инкапсуляция предполагает закрытие доступа к переменной вне класса. Для реализации инкапсуляции используем блок do ... end. Несколько не обычный ход, не правда ли? Проверим содержимое таблицы __parent класса cMySecondClass: Код for i,v in pairs(cMySecondClass.__parent) do SendToAll("["..i.."] = "..tostring(v)) end На экране видим следующее: Цитата [mMember2] = first_class_mMember2 [mMember1] = 5 [func1] = function: 01A78A60 [func2] = function: 01A62500 [__index] = table: 01A454B8 [__call] = function: 01A78AD0 На переменные __index и __call можем не обращать внимания (они являются метаметодами). Теперь, для чего всё это нужно? А нужно это для значительной оптимизации работы и для структурированного написания скриптов. Дело вот в чём. Создавая класс cMyFirstClass, под него выделяется определённое количество памяти. Память, выделенная под класс cMySecondClass, - это память выделенная под переменные mMember2 и func3, а подо все унаследованные переменные память не выделяется (она уже выделена под класс cMyFirstClass) (тут имеется ввиду выделяемая память под реализацию функций). Поэтому получаем большую функциональность при малом выделении памяти и при малом количестве строк реализации. В конце вопрос на засыпку: Что выведет на экран метод cMySecondClass:func1() ? Ответ: second_class_mMember2 |
|
|
![]() ![]()
Сообщение
#3
|
|
![]() RusHub team lead ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз ![]() |
Библиотека построения именованых классов от разработчика lua.
Самая продвинутая реализация классов и практически всех принципов ООП, включая виртуальные классы! ![]() Упрощённая библиотека построения безымянных классов от разработчика lua. ![]() В дальнейшем мною будут выложены подкорректированные варианты, так как в данных вариантах мною были найдены мелкие недочёты. |
|
|
![]()
Сообщение
#4
|
|
![]() RusHub team lead ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз ![]() |
Для того чтобы проследить работу с библиотекой классов и понять как всё это работает, предлагаю продебажить следующий простой пример. Дебажить можно с помощью средств LuaEdit.
Код class.Account() function Account:__init(initial) self.balance = initial or 0 end function Account:deposit(amount) self.balance = self.balance + amount end function Account:withdraw(amount) self.balance = self.balance - amount end function Account:getbalance() return self.balance end ----------------------------------- class.NamedAccount(shared(Account)) -- shared Account base function NamedAccount:__init(name, initial) self.Account:__init(initial) -- 10.00 self.name = name or 'anonymous' -- 'John' end ----------------------------------- class.LimitedAccount(shared(Account)) -- shared Account base function LimitedAccount:__init(limit, initial) self.Account:__init(initial) -- 10.00 self.limit = limit or 0 -- 0.00 end function LimitedAccount:withdraw(amount) if self:getbalance() - amount < self.limit then error 'Limit exceeded' else self.Account:withdraw(amount) end end ----------------------------------- class.NamedLimitedAccount(shared(NamedAccount), shared(LimitedAccount)) function NamedLimitedAccount:__init(name, limit, initial) self.NamedAccount:__init(name, initial) -- 'John', 10.00 self.LimitedAccount:__init(limit, initial) -- 0.00, 10.00 end -- widthdraw() disambiguated to the limit-checking version function NamedLimitedAccount:withdraw(amount) return self.LimitedAccount:withdraw(amount) end ----------------------------------- myNLAccount = NamedLimitedAccount('John', 0.00, 10.00) myNLAccount:deposit(2.00) print('balance now', myNLAccount:getbalance()) --> 12.00 myNLAccount:withdraw(1.00) print('balance now', myNLAccount:getbalance()) --> 11.00 --myNLAccount:withdraw(15.00) --> error, limit exceeded ![]() |
|
|
![]()
Сообщение
#5
|
|
![]() RusHub team lead ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз ![]() |
Доработки:
1) Добавлена возможность для описания функций и членов класса "внутри" самого класса. Идентичные примеры: Код class.Account() function Account:__init(initial) self.balance = initial or 0 end function Account:deposit(amount) self.balance = self.balance + amount end function Account:withdraw(amount) self.balance = self.balance - amount end function Account:getbalance() return self.balance end Код class.Account { __init = function(self, initial) self.balance = initial or 0 end; deposit = function(self, amount) self.balance = self.balance + amount end; withdraw = function(self, amount) self.balance = self.balance - amount end; getbalance = function(self) return self.balance end; } Правила для объявления классов: Код class.MyClass([inherited,] [{members}]) Код [local] MyClass = class(["MyClass",] [inherited,] [{members}]) 2) Изменено название функции shared. Новое название: virtual. Думаю, что новое название более логично, а старое может только запутать. Данная функция служит для построения виртуальных классов, и она в точности реализует механизм виртуального наследования классов, как это происходит в языке С++. 3) Для того, чтобы таблица class приобрела свойства зарезервированного слова, запрещена запись в эту таблицу. Данная таблица используется исключительно для построения классов. 4) Исправлены все внутренние поля и локальные переменные с shared на is_virtual, дабы не вносить непонятности. ![]() |
|
|
![]()
Сообщение
#6
|
|
![]() RusHub team lead ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз ![]() |
Примеры использования классов:
(Быстро накатал - не судите строго) Код class.cutility { SomeFunction = function(self) end; } class.cbase { } class.cplagin (cutility, cbase, { name; desc; __init = function(self, sName, sDesc) self.name = sName self.desc = sDesc end; }) --создание объектов класса mPlagin = cplagin("myFirstPlagin", "ForTest") -- вызов конструктора __init Существую классы, в которых мы можем описывать то, что нам нужно и существуют объекты, которые строятся на основании классов. Свой класс можно создать двумя способами: 1) Код class.NameOfClass ([<наследуемый_класс>, <наследуемый_класс>, ..., ]{таблица с элементами класса}) в [скобках] - необязательные параметры 2) Код NameOfClass = class(["NameOfClass", ][<наследуемый_класс>, <наследуемый_класс>, ... , ]{таблица с элементами класса}) в данном примере класс cplagin наследует классы cutility и cbase. Поэтому в классе cplagin мы можем вызывать функции классов cutility и cbase. Да, совсем забыл сказать, что для использования классов нужно подключить файл class.lua, который выложен в топике: http://mydc.ru/index.html?showtopic=1429&a...ost&p=15699 Файл кладётся в папку libs (либо в папку с ptokax.exe). Подключается так: Код require"class" Такое подключение файла в lua аналогично подключению заголовочный файлов в с++ |
|
|
![]() ![]()
Сообщение
#7
|
|
![]() RusHub team lead ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз ![]() |
Глобальные функции и переменные модуля class.lua
Глобальные переменные: class - "конструктор" класса. Переменная, отвечающая за создание класса. Два эквивалентных метода создания простого класса: 1) Код class.MyClass{} 2) Код MyClass = class("MyClass", {}) Глобальные функции: virtual(class) - функция, превращающая обычного класса в виртуальный класс. typeof(value) - расширенная функция определения типа переменной. Возвращает "class", если переменная является классом, "virtual_class" - если переменная представляет из себя виртуальный класс, "object" - если переменная является объектом какого-то класса, а во всех остальных случаях возвращает lua тип переменной, то есть работает как обычная lua функция type(). classof(value) - функция возвращает переменную, если она является классам, в противном случае возвращает nil. classname(value) - функция возвращает имя класса, если переменная является именованым классом, иначе возвращает nil. implements(value, class) - функция проверяет, что объект или класс (value) поддерживает интерфейс целевого класса (class). Это означает, что объект или класс может быть подставлен в качестве аргумента в функцию, которая ожидает целевой класс. Мы рассматриваем только элементы класса, которые могут вызываться из класса, то есть вызываемые элементы (функции и таблицы). is_a(value, class) - функция проверяет содержится ли указанный объект или класс (value), в целевом классе (class). Проверка начинается с самого объекта (или класса), и идёт по цепочке наследования вглубь до целевого класса. Если целевой класс найден, то происходит проверка поддержки интерфейсов (данная функция может вернуть false в механизме множественного наследования, из-за неоднозначностей). |
|
|
![]()
Сообщение
#8
|
|
![]() Абсолютный новичок Группа: Пользователи Сообщений: 1 Регистрация: 20.10.2013 Пользователь №: 11 741 Спасибо сказали: 0 раз ![]() |
Огромная благодарность за предоставленый материал
|
|
|
![]()
Сообщение
#9
|
|
![]() Абсолютный новичок Группа: Пользователи Сообщений: 1 Регистрация: 9.9.2014 Пользователь №: 12 504 Спасибо сказали: 0 раз ![]() |
Большое спасибо за материал. Оказался очень полезен.
|
|
|
![]()
Сообщение
#10
|
|
![]() KEEP CLEAR AT ALL TIMES ![]() ![]() ![]() ![]() Группа: Пользователи Сообщений: 141 Регистрация: 4.9.2011 Из: Беларусь, Минск Пользователь №: 9 667 Спасибо сказали: 3 раза ![]() |
Очень бы хотелось уловить суть ООП на примерах скриптов с минимальным функционалом, но реализованным через ООП, для той же Птоки, например.
Если я правильно выражаюсь: 1) в одном скрипте "показать" (сделать упор) на инкапсуляцию; 2) в другом – на наследование; 3) в третьем – на полиморфизм; 4) ну и наконец – всё вместе. А так, для меня, например, это какая-то абстракция, а как к ней подступиться – неизвестно. P.S.: не похерьте эту тему, а лучше прикрепите её к данной. |
|
|
![]() ![]() |
Похожие темы
Тема | Ответов | Автор | Просмотров | Последнее сообщение | |
---|---|---|---|---|---|
![]() |
![]() От темы с ID: 1429 |
5 | Setuper | 10 698 | 3.5.2009, 18:20 Посл. сообщение: Setuper |
![]() |
Сейчас: 8.9.2024, 2:41 |