Перейти к публикации
Форум - ComputerCraft
Zer0Galaxy

Объектно-ориентированное программирование и Lua

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

В данной статье я хотел бы показать, как можно применять объектно-ориентированный подход при программировании на lua. Если Вы ранее не сталкивались с ООП в других языках, статья вряд ли станет Вам полезной ибо не предназначена для обучения объектно-ориентированному мышлению, а лишь приводит пример реализации объектов.

Lua не относится к объектно-ориентированным языкам, поскольку не содержит стандартных механизмов создания и использования объектов. Но lua-таблица является настолько гибким инструментом, что позволяет реализовать практически любую структуру, присущую другим языкам. В том числе и объекты.

 

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

ClassA={}
ClassA.__index=ClassA
По большому счету, называть это классом нельзя, поскольку класс — это абстракция, служащая шаблоном для создания объекта. Сдесь же мы имеем дело с вполне реальной таблицей. Поэтому ClassA будем называть объектом-прототипом.

Как видите, я не описал никаких полей объекта-прототипа т. к. в отличии от таких языков как Delphi и C++, поля таблицы в lua можно описывать когда угодно, а не только при создании таблицы. Поля будем прописывать в конструкторе класса при создании экземпляра объекта. Единственное поле __index содержит указатель на сам класс. Это нужно будет для правильной работы оператора self.

 

Создадим конструктор:

function ClassA:new(_Name)
  local obj={Name = _Name}
  setmetatable(obj,self)
  print('Constructor ClassA for '..obj.Name)
  return obj
end
Что делает конструктор?

Строка 1. Создает экземпляр теперь уже объекта, описывает поля объекта и присваивает полям начальные значения.

Строка 2. Переопределяет метатаблицу вновь созданного объекта, в результате чего объект получает доступ к методам класса. Методов пока нет, но они будут описаны позже.

Строка 3. Это необязательная строка. Я ее сюда вставил для демонстрации работы конструктора.

Строка 4. Возвращает созданный объект.

 

Теперь создадим парочку методов:

function ClassA:Metod1()
    print('Metod1 of ClassA for '..self.Name)
end

function ClassA:Metod2()
    print('Metod2 of ClassA for '..self.Name)
end
Зачем два? В дальнейшем один из них будет наследован, а другой перекрыт методом наследника.

 

И так, мы создали объект-прототип. Пора создавать экземпляры объекта. Экземпляры будем хранить в таблице objects. Для создания экземпляра вызываем конструктор

objects={}

for i = 1,2 do
    objects[i] = ClassA:new('Object#'..i)
end

Смотрим что получилось

for i=1,2 do

    objects[i]:Metod1()
    objects[i]:Metod2()
end

Вот программа в полном сборе.

ClassA={}
ClassA.__index=ClassA

function ClassA:new(_Name)
    local obj={Name = _Name}
    setmetatable(obj,self)
    print('Constructor ClassA for '..obj.Name)
    return obj
end

function ClassA:Metod1()
    print('Metod1 of ClassA for '..self.Name)
end

function ClassA:Metod2()
    print('Metod2 of ClassA for '..self.Name)
end

objects={}
for i = 1,2 do
    objects[i] = ClassA:new('Object#'..i)
end

for i=1,2 do
    objects[i]:Metod1()
    objects[i]:Metod2()
end

Если все сделано правильно, после запуска программы Вы должны наблюдать вот такие сообщения:

[ATTACH]115[/ATTACH]

В следующий раз я расскажу о наследовании методов объекта и их полиморфизме.

post-6-14160784008126_thumb.jpg

  • Like 1

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


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

В прошлый раз мы научились создавать объекты в lua и вызывать их методы. Нами был создан класс ClassA (хорошо, не класс, а объект-прототип), имеющий одно поле (Name) и два метода (Metod1, Metod2). Были созданы два экземпляра объектов и вызваны их методы.

Сегодня мы попытаемся создать наследника от ClassA, добавим ему еще одно поле, унаследуем один из методов без изменений, а второй переопределим.

Так же как и класс-родитель, наследник изначально представляет собой пустую таблицу:

ClassB={}
ClassB.__index=ClassB
setmetatable(ClassB,ClassA)
После выполнения setmetatable, ClassB внешне будет представлять собой точную копию ClassA, т. е. унаследует все его методы.

Изменения начнем с конструктора

function ClassB:new(_Name,_Size)
  local obj=ClassA:new(_Name)
  obj.Size=_Size
  setmetatable(obj,self)
  print('Constructor ClassB for '..obj.Name)
  return obj
end
Каковы отличия в конструкторах родителя и наследника? Как видим, для создания объекта наследник использует конструктор родителя, а не создает объект «с нуля». Это конечно не обязательно, но такова классика. Другое отличие — новое поле, которое будет хранить воображаемый размер нашего объекта.

Переходим к методам. Metod1, как и договаривались оставляем нетронутым, а вот Metod2 переопределим.

function ClassB:Metod2()
    ClassA.Metod2(self) --inherited
    print('Metod2 of ClassB for '..self.Name..'. Size is '..self.Size)
end
Тут я показал как из метода объекта-наследника вызвать метод объекта-родителя. Но это тоже при необходимости.

Вот собственно и всё.

Помимо ранее созданных объектов ClassA, создаем еще два экземпляра нового объекта

objects={}
for i = 1,2 do
    objects[i] = ClassA:new('Object#'..i)
end

for i = 3,4 do
    objects[i] = ClassB:new('Object#'..i, i*i)
end
И посмотрим что получилось

for i=1,4 do
    objects[i]:Metod1()
    objects[i]:Metod2()
end
У меня получилось вот что:

[ATTACH]116[/ATTACH]

Обращаю внимание, что для объектов 3 и 4 конструктор и Metod2 выполняются дважды — сначала от ClassA, затем от ClassB.

 

Мы рассмотрели такие особенности ООП как наследование и полиморфизм. А вот как реализовать на lua сокрытие данных объекта от доступа извне (по моему, это называется инкапсуляцией) я пока не знаю. Если знаете — пишите.

 

Для тех кто все таки решил попробовать себя в объектно-ориентированном программировании, напоминаю — следите за пунктуацией. Имя поля отделяем точкой, имя метода — двоеточием. Это важно.

post-6-14160784008394_thumb.jpg

  • Like 2

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


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

Нашел в Блоге еще записи о программировании в стиле ООП на луа, но там как-то все сумбурно.  Дмитрий, Вячеслав. Просьба! Напишите понятным языком, как оно должно выглядеть на примере. Я там попросил в блоге Вячеслава, чтобы вы нам, нубасам, объяснили про метаметоды и прочее, приват и паблик свойства, как избавиться от двоеточия без явного указания аргумента self или чего там.  Вот мол, молоток, вот гвоздь, забивается он так-то и так-то. Не знаю, правда, нужна ли эта абракадабра для наших черепашек :) , я лично обхожусь программками маленькими, написанными в процедурном стиле, мне этого хватает с головой, но все же.

 

Видимо для больших проектов, сложных графических интерфейсов и прочего можно применить парадигму ООП. Если там грамотный и мощный класс сконструирован с кучей полезных методов, почему и не использовать их при создании уже своих классов с наследованием. Может, народ будет создавать объекты с описанием свойств реакторов, и писать им методы отгрузки топлива и управления мощностью. Не сильно даже пока понимаю, где оно нужно.

 

вот, что пока понятно простому обычному человеку и мне в частности, если что не так, поправьте, что я тут написал:

term = require('term')
term.clear()

--Создадим два класса, родительский и класс потомок


-- ==========Cоздадим класс Игроки, это родительский класс===============
-- примечание: Я создал класс Игроки, но это не абстрактный класс, так как он позволяет создавать непосредственно
-- экземляр класса, то есть конкрентый объект.  Абтрактные классы можно только наследовать и они должны
-- иметь хотя бы один абстрактный метод, но и производным классом его назвать нельзя, видимо, так как он ни от 
-- какого класса не наследует ни свойств, ни методов.
print('============ ВЫПОЛНЕНИЯ ПРОГРАММКИ  ===========')


ClassPlayers = {} 

-- теперь проинициализируем поля, процедуры и фунции нашего класса 
function ClassPlayers:new(age, uuid, ip) 
	local obj = {} 
	obj.age = age or nil -- возраст, по умолчанию nil 
	obj.uuid = uuid or 'uuid' -- по умолчанию 'uuid' 
	obj.ip = ip or '1.1.1.1' -- по умолчанию '1.1.1.1'
	
	--далее превращаем таблицу в класс
	setmetatable(obj,self) 
	self.__index = self
	return obj -- возвращаем наш объект (экземпляр класса)
end 

-- функция получения свойств 
function ClassPlayers:get() 
	return self.age, self.uuid, self.ip
end 

-- функция изменения свойств
function ClassPlayers:set(age, uuid, ip) 
	self.age = age 
	self.uuid = uuid
	self.ip = ip
end 


-- создадим наших первых игроков! Это конкретные объекты, экземпляры класса Игроки
Alex = ClassPlayers:new(36, "abc-zxc-1234567-vvv", '192.168.1.0') -- мы создали объект Алекс, у которого есть какие-то свойства
Bob = ClassPlayers:new(28, "zxc-qwe-0987654-bbb", '192.168.1.2') -- мы создали объект Боб, у которого есть какие-то свойства

print('>>> проверим объект Алекс')
print(Alex:get())


print('>>> меняем Алекса и проверяем, что получилось')
Alex:set( 35, '111-aaa-bbb', '192.168.1.1') 
print(Alex:get()) 




-- =========Создадим подкласс Администраторы==========
ClassAdmins = {} 

--пусть у этого подкласса будет свой метод Бан и метод Бла-бла, а так же один переназначенный метод гет
function ClassAdmins:blabla() 
	print('Отлично, метот Бла-бла у класса \'Админы\' выполнился') -- здесь может быть все, что угодно, по идее
end

--Админы имеют также медод Бан
function ClassAdmins:ban(user) 
	print ('Юзер '..user..' забанен навеки вечные!!!')
end 

--переназначение метода гет у подкласса Админы
function ClassAdmins:get() 
	return "Это злые админы, всех банят и удаляют моды :)" 
end 

--наследуемся от родительского класса Игроки
setmetatable(ClassAdmins,{__index = ClassPlayers}) 

-- Создаем дефолтного пока игрока ГрингоСтар без свойств, здесь видно, что метод new используется у родительского класса Игроки. Он там уже один раз
-- описан, и его не нужно снова писать в коде и классе для админов. Это, cудя по всему, ключевой момент ООП.

print('>>> Создаем и проверяем нашего Гринго, пока это какая-то какашка по умолчанию')  -- но это уже самостоятельный объект
GringoStar = ClassAdmins:new() --создали админа Гринго со стандарными свойствами ИГРОКА, мы не задали пока ему свойств, хоть и можно было!!!
print(ClassPlayers.get(GringoStar))

print('>>> Меняем нашего Гринго так как ему положено быть')
GringoStar:set(23, "555-666-sssssss-uuu", '192.168.255.255') -- изменили Гринге свойства, метод set ТОЖЕ описан уже в классе ИГРОКИ!!!

print('>>> Вызовем метод гет у Админов')
print(ClassAdmins:get())

print('>>> Вызовем метод гет у Игроков для нашего Гринго')
-- синтаксис: РодительскийКласс.Метод(сам_объект, параметры)
print(ClassPlayers.get(GringoStar))

print('>>> Вызовем метод Бла-бла у Админов')
ClassAdmins:blabla() 

print('>>> Вызовем метод Бан у Админов и в качестве аргумента\nпередадим на растерзание дюпера Васю из 3-А')
ClassAdmins:ban('Вася') 

print('>>> обращаюсь к некоторому свойству Алекса')
print(Alex.age)

print('>>> Меняю его на 50, Alex.age = 50')
Alex.age = 50

print('>>> смотрю свойство age Алекса   : ',Alex.age)
print('>>> смотрю свойство uuid Гринго  : ',GringoStar.uuid)

---Что получилось? Здесь, по идее и замыслу ООП, Гринго обладает всеми свойствами и методами класса Игроки, то есть наследует их,
---а также имеет свои свойтсва и методы класса Админы.

вот, что выдала программка:

 

DfOjWjr.png

 

Просьба, можно ли так же с объяснениями написать, если не затруднит о том, что я просил выше, на конкретном примерчике?

  • Like 1

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


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

Лучше изучить ооп на Java, а потом применять знания эти в луа, так как тут псевдо ооп, изучать ооп в луа испортит все представление об ооп.

  • Like 1

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


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

Лучше изучить ооп на Java, а потом применять знания эти в луа, так как тут псевдо ооп, изучать ооп в луа испортит все представление об ооп.

 

Это понятно. Изучать что-то можно всю жизнь, век живи, век учись, все равно нубом помрешь, как сказал Абдулла ибн Абдурахман. 

И это если ты по специальности программист.

 

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

 

Нео, это же игра просто.  Ты через чур ее близко к сердцу сильно прижал. За сервера переживаешь, браузеры на ОС и пинг стабильный чтобы был у твоей ОпенНет. 

 

Все равно что шутер-геймер спросил, какие перки на прокачку плазмогана есть и какие лучше пульки юзать, а ты ему говоришь.  Э-э-э-э-э-э-э, чува-а-а-ак, какие пульки, лучше в школу "Морских котиков" в Детройте записывайся и станешь мега-крутым снайпером, дадут пукалку настоящую :giggle:

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


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

Вячеслав. Просьба! Напишите понятным языком, как оно должно выглядеть на примере.

Я ООП сам не открывал а искал в гугле. Я могу сделать статью из копипасты с гугла. Сойдет?

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


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

Я ООП сам не открывал а искал в гугле. Я могу сделать статью из копипасты с гугла. Сойдет?

Конечно сойдет!  В нете то любой полазить может и инфы там много всякой и винегрета.

Но важен пример конкретный. Вот, мол, готовый класс с наследованием для  программки для робота, печки, реактора и прочего(с комментариями в коде) Методы не нужны подробные. Важна структура только. Что за подчеркивания одинарные и двойные, что они значат, можно ли скрыть(запретить) изменение свойств вне класса, метаметоды, как уйти от двоеточий, я не сильно понял в той статье, что там за абракадабра. Ты же сам говоришь, что можно так сделать как-то и т.п.

 

У меня солдаты, некоторые не умели читать и писать толком, управляли многомиллионной по стоимости военной техникой высокоточной сложнейшей радиоэлектронной, подстраивали ее и выполняли боевые задачи повышенной важности и сложности по отработке нормативов и выдаче радиолокационной информации о воздушных целях и несли боевое дежурство. Но не имели понятия о транзисторах и законе Ома даже. А знаешь почему?  Потому что практически я им показал и рассказал, как и что делать, просто на пальцах объяснил и натренировал их до автоматизма, как обезьян. Понимаешь меня? :)

 

Думаю, что для ОпенГлассес вполне себе можно написать в стиле ООП несколько мощных классов и пусть народ их юзает на проекте нашем в свое удовольствие, так ведь?

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


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

Создайте аккаунт или войдите в него для комментирования

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

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас

×