myDC.ru

Здравствуйте, гость ( Вход | Регистрация )

 
 
Ответить в данную темуНачать новую тему

> Объектно-Ориентированное Программирование в Lua, Создание и использование классов

Setuper
сообщение 13.12.2008, 15:01
Сообщение #1


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1707 раз




Объектно-Ориентированное Программирование (ООП) в LUA

В данной теме обсуждаются мощнейшие методы ООП big_smile.gif


Итак, потихоньку буду писать основные понятия.

Объект и класс

Прежде всего следует понять что такое объект, и что такое класс

Согласно языку С++, класс - это определяемый пользователем тип, а объект - это экземпляр класса.
Попробуем в 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 подчёркивание спереди.



продолжение следует...


Спасибо сказали:
Go to the top of the page
+Quote Post
Setuper
сообщение 27.2.2009, 0:32
Сообщение #2


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1707 раз




ООП широко распространено из-за его эффективности. Эта эффективность выражается в экономном использовании памяти и ресурсов компьютера. Достигается это всё при помощи таких мощных аппаратов как инкапсуляция, наследование и полиморфизм и абстракция.

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

Итак, во-первых, не вдаваясь в подробности, напишу основную функцию, которая содержит в себе все выше изложенные аппараты.
Те, кто достаточно хорошо знают язык LUA, могут попытаться понять как это всё работает, ну а всем остальным нужно только скопировать без понимания - понимать нужно будет дальше (после этого кода).
Код
local _G = _G
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
Приведённая функция (cClass) представляет из себя функцию для реализации класса.


Давайте же попробуем написать свой класс, используя эту самую функцию:
Код
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
[func3] = function: 01A12170
[__parent] = table: 01A640F0
то есть, мы видим элемент mMember2 и метод func3, но также мы можем наблюдать некую таблицу __parent. Это таблица базового класса (cMyFirstClass).




Полиморфизм.
К любым элементам базового класса можно обращаться через эту самую таблицу __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


Спасибо сказали:
Go to the top of the page
+Quote Post
Setuper
сообщение 17.5.2009, 23:02
Сообщение #3


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1707 раз




Библиотека построения именованых классов от разработчика lua.
Самая продвинутая реализация классов и практически всех принципов ООП, включая виртуальные классы!
Прикрепленный файл  classlib.lua ( 11.8 килобайт ) Кол-во скачиваний: 90


Упрощённая библиотека построения безымянных классов от разработчика lua.
Прикрепленный файл  unclasslib.lua ( 10.84 килобайт ) Кол-во скачиваний: 48


В дальнейшем мною будут выложены подкорректированные варианты, так как в данных вариантах мною были найдены мелкие недочёты.


Спасибо сказали:
Go to the top of the page
+Quote Post
Setuper
сообщение 20.5.2009, 14:05
Сообщение #4


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1707 раз




Для того чтобы проследить работу с библиотекой классов и понять как всё это работает, предлагаю продебажить следующий простой пример. Дебажить можно с помощью средств 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


Прикрепленный файл  screen.jpg ( 291.54 килобайт ) Кол-во скачиваний: 530


Спасибо сказали:
Go to the top of the page
+Quote Post
Setuper
сообщение 20.5.2009, 17:41
Сообщение #5


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1707 раз




Доработки:

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, дабы не вносить непонятности.

Прикрепленный файл  class.lua ( 12.85 килобайт ) Кол-во скачиваний: 98


Спасибо сказали:
Go to the top of the page
+Quote Post
Setuper
сообщение 7.6.2009, 18:40
Сообщение #6


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1707 раз




Примеры использования классов:
(Быстро накатал - не судите строго)

Код
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 аналогично подключению заголовочный файлов в с++


Спасибо сказали:
Go to the top of the page
+Quote Post
Setuper
сообщение 10.6.2009, 15:55
Сообщение #7


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1707 раз




Глобальные функции и переменные модуля 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 в механизме множественного наследования, из-за неоднозначностей).


Спасибо сказали:
Go to the top of the page
+Quote Post
serg_58
сообщение 20.10.2013, 15:35
Сообщение #8


Абсолютный новичок


Группа: Пользователи
Сообщений: 1
Регистрация: 20.10.2013
Пользователь №: 11 741
Спасибо сказали: 0 раз




Огромная благодарность за предоставленый материал
Go to the top of the page
+Quote Post
xnt
сообщение 9.9.2014, 12:19
Сообщение #9


Абсолютный новичок


Группа: Пользователи
Сообщений: 1
Регистрация: 9.9.2014
Пользователь №: 12 504
Спасибо сказали: 0 раз




Большое спасибо за материал. Оказался очень полезен.
Go to the top of the page
+Quote Post
MIKHAIL
сообщение 1.7.2016, 9:24
Сообщение #10


KEEP CLEAR AT ALL TIMES
****

Группа: Пользователи
Сообщений: 137
Регистрация: 4.9.2011
Из: Беларусь, Минск
Пользователь №: 9 667
Спасибо сказали: 2 раза




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

Если я правильно выражаюсь:
1) в одном скрипте "показать" (сделать упор) на инкапсуляцию;
2) в другом – на наследование;
3) в третьем – на полиморфизм;
4) ну и наконец – всё вместе.

А так, для меня, например, это какая-то абстракция, а как к ней подступиться – неизвестно.


P.S.: не похерьте эту тему, а лучше прикрепите её к данной.


Спасибо сказали:
Go to the top of the page
+Quote Post

Ответить в данную темуНачать новую тему
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

Collapse

> Похожие темы

  Тема Ответов Автор Просмотров Последнее сообщение
No New Posts Topic has attachmentsОт: Объектно-ориентированные Принципы В Lua
От темы с ID: 1429
5 Setuper 6 033 3.5.2009, 18:20 Посл. сообщение: Setuper

 



RSS Сейчас: 16.10.2018, 14:03