[lua] ООП и все все все

CryDeS

Друзья CG
14 Июл 2015
1,210
11
Итак, я таки добрался до ООП в луа, и собственно сейчас расскажу о нем вам, запаситесь попкорном(и валидолом если вы знали про ООП до прочтения этой статьи)
Для начала вкратце и понятным языком расскажу что такое ООП вообще, и что из себя представляет.
ООП, оно же объектно-ориентированное программирование, это такой способ написания программ(в нашем случае скриптов), при котором активно используются некоторые концепции, аля правила.
Основных правил в ООП немного, и созданы они для удобства кода и его дальнейшего использования. Основных понятий в ООП два, это объект и класс, начну с последнего.
Класс, это такая штуковина, которая хранит в себе какие то данные и функции для работы с ними. Например
Код:
Класс_1 = {
	ЧИСЛО: целое
	Функция добавить(x)
	{
		ЧИСЛО = ЧИСЛО + x
	}
}
Этот класс хранит в себе одну переменную «ЧИСЛО» типа целое, и один метод(функцию) для работы с этим классом, а конкретнее для добавление переменная класса «ЧИСЛО» какого-то другого числа.
Теперь о объектах. Объект - экземпляр класса. Если класс описан выше, то объект это:
Код:
...(где то внутри кода)...
	числа = new Класс_1[50]
...(дальше код)...
так вот массив числа и есть объект класса Класс_1.
Нахрен это вообще все нужно? Ответ на этот вопрос прост, для удобства программирования. Например у нас есть игровой клуб в котором есть несколько компьютеров, с разными характеристиками. Так вот каждую характеристику нужно хранить в переменной, а компьютеров много, как и характеристик. А делать код в виде a:array, b :array … z:array, где a,b … z это характеристики не очень удобно, проще сделать класс, куда можно будет сразу и добавить некоторые функции для работы с ним, например проверку компьютера на занятость(сидит ли кто за ним)
Код:
Класс_Компьютер = {
	Номер: число
	Процессор: строка
	…
	ОЗУ: число
	Занят: булевый тип 
	Функция Занят_ли_комп(): булевый тип
	{
		если занят тогда возвращаем истину (1), иначе возвращаем ложь(0)
	}
	Функция ЗанятьКомп(): пустой тип
	{
		Занят = истина
	}
}
Далее внутри кода мы можем запросить у объекта занят ли он, например
Код:
...(там где то вдалеке были объявлены и занесены данные о компьютерах, а сейчас вы выпинываем из-за компьютера кого то)...
	если комп[3]:Занят_ли_комп() = ложь тогда комп[3]:ЗанятьКомп()
...(там чото еще будет происходить)
Это вкратце о объектах и классах, если обобщить то класс это сборник свойств некоторых нужных в будущем нам объектов, и мы можем создать объект определенного класса, получив доступ ко всем методам(функция) данного класса, и всем его данным.
Теперь же перейдем к основным правилам ООП, их всего четыре, но со страааааашными названиями(НЕ ПУГАТЬСЯ!) и простой сутью, вот они:

  • [li]Абстракция[/li]
    [li]Инкапсуляция[/li]
    [li]Наследование[/li]
    [li]Полиморфизм[/li]
Я ГОВОРИЛ НЕ ПУГАТЬСЯ, ТАК ЧТО ПРЕКРАТИ СУДОРОЖНО ЖАТЬ НА КНОПКУ ЗАКРЫТЬ ЭТУ ВКЛАДКУ
А теперь разберем их по полочкам, что бы было понятнее.
Абстракция
Из своего названия довольно очевидно вытекает смысл, необходимо создавать такие структуры, на которые мы будем абстрагироваться. К примеру любой класс является абстракцией, по факту это совокупность структур данных и функций для работы с ними, которые по факту являются %вставьте_более_глубокую_фигню%.

Инкапсуляция
Не люблю это слово. Просто не люблю, но да ладно.
Это такой способ написания, когда пользователь видит только готовую оболочку, а все костыли, быдлокодик, и прочее дерьмо остается спрятанным от лишних глаз. Накосячил? НЕ ЗАМЕТЯТ! Круто, правда?
Чаще всего используется два+один метода, public(общедоступный), private(только для этого класса и нигде и никогда извне, обломитесь, индусы!), так же встречается protected(защищенный от кривых рук быдлокодеров) метод, который по сути добавляет всем наследникам возможность пользоваться этим методом.

Наследование
Довольно полезная штука, и думаю по названию крайне очевидная, даже поочевиднее абстракции, ну так вот, заключается наследование в ООП в том, что мы можем создать класс являющийся потомком(ребенком) другого класса, сохраняя свойства родительского класса и методы родительского класса. Например у нас есть класс техника, создадим класс "техника" и добавим свойства "колеса" и "руль", будет "автомобиль"(очень грубо, но всем на...). Добавим крылья, будем класс "самолет", и так далее.
Так вот, наследование нужно когда нам необходимо на базе какого либо уже созданного класса сделать новый, который будет на 95% похож на старый, но с одной своей фичей!(блекджек и ... не включаются, хотяя ...)

Полиморфизм
Забудьте про морфа, его тут нет. Хотя бы на время чтения этой части :)
Если немного оффицально, то это такая вещь, которая гарантирует нам один интерфейс на разные классы и типы. Не очень понятно? Тогда к примеру!
Допустим у нас есть две матрицы матрицы(массив NxM). И нам их нужно будет по ходу дела перемножать мнооооого мноооого раз. А делается это муторно(кому интересно как, гуглите, мне лень рассказывать :p, выходит около 5-10 строк кода).
Так что нам будет проще переписать операцию простого умножения "*" для работы с матрицами.
Код:
__add(x,y)
{
	...(тут должен быть код по умножению матрицы x на матрицу y, но к сожалению индийские программисты работающие за еду украли его) ...
}
И далее по ходу программирования, мы будем умножать матрицы просто, matrix1 * matrix2. Удобно? Чертовски удобно, я бы сказал.

Это и есть основные концепции ООП. О том стоит ли их использовать и насколько они удобны будем сраться в другой раз, и для этого есть много статей, форумов и т.д, тут мы только разберем некоторые их части и их реализацию на lua. А вы думали вы на форму по php попали, ребята?

Сразу оговорюсь, начиная читать эту часть без знания что такое ООП хотя бы отдаленно не стоит, так как тут будет прямое объяснение реализации ООП в lua.
Итак, как известно(кому не известно вперед гуглить) в lua на все дела есть таблицы. И класс тут реализуется именно как таблица. Вот и пример подъехал(спасибо doter.ua же за то что придумал за меня его :3)
Код:
Hero = { -- родительский класс/таблица
  name = "name",
  level = 1,
  myPrint = function(me)
   print ( "I'm ".. me.name ..", my lvl is ".. tostring(me.level) )
	 -- I'm name, my lvl is 1
  end
}
Это класс Hero, с одной функцией внутри, вывода форматированого сообщения. Вся забавность ООП в lua состоит в том, что класс тут является одновременно и объектом. Не создав объект не сделаем класс, что с точки зрения ООП неприемлимо, а с точки зрения практики вполне "это не баг, а фича!". По этому в lua как такового ООП НЕТ! Есть его имитация, чисто ради удобства. И этого нам вполне хватит.
Думаю суть абстракции понятен, по факту у нас тут есть переменная строковая, численая, и какая то функция. А для нас это совокупность отображающих героя! Вот и асбтрагировались от суеты и тлена
Инкапсуляция в данном примере отсутствует, так что приведу усложненый пример, с private методом.
Код:
Hero = { -- родительский класс/таблица
  name = "name",
  level = 1,
  local private = {}
	private.game = "DOTA 2"
  myPrint = function(me)
   print ( "I'm ".. me.name ..", my lvl is ".. tostring(me.level) .. " and i'am from game" .. private.game )
	 --I'm name, my lvl is 1 and i'am from game DOTA 2
  end
}
Тут к переменной game подобраться будет извне нельзя. (Честно, незнаю на какой вам черт может понадобится инкапсуляция для вашего гейммода, она тут бесполезна, ну знать что ее сделать можно вы будете знать)

Наследование! FUCK YEAH MOTHERFUCKERS
Пожалуй самая удобная часть ООП наравне с полиморфизмом(снова забудьте о морфе епрст!).
И тут мы подходим к чертовски удобной функции lua, а именно к setmetateable(child_table, parent_table_index)
Что позволяет нам эта функция? Устанавливать родительскую таблицу другой таблице! С сохранением всех полей естественно!
И сразу к примеру!
Код:
Hero = { -- типа родительский класс/таблица
  name = "name",
  level = 1,
  myPrint = function(me)
   print ( "I'm ".. me.name ..", my lvl is ".. tostring(me.level) )
  end
}
local tide = {} -- типа наследник
local axe = {} -- типа еще один

setmetatable(tide, {__index = Hero})
setmetatable(axe, {__index = Hero})

axe.level = 10
axe.name = "Axe"
tide.level = 25
tide.name = "Tide"

print( Hero:myPrint() )
print( tide:myPrint() )
print( axe:myPrint() )
-- I'm name, my lvl is 1
-- I'm Tide, my lvl is 25
-- I'm Axe, my lvl is 10
Собственно отсюда можно понять как и пользоваться setmetatable.
Однако, для упрощения можно сделать довольно удобную глобальную функцию:
Код:
function SetParentTable(table, table2)
{
	setmetatable(table, {__index = table2})
}
И что бы не прописывать кучу раз __index = table, мы будем писать просто эту функцию, удобнее же, ну.

Полиморфизм? Полиморфизм!
Давайте переопределим функцию в примере выше(функцию myPrint), и сломаем ее к черту! ДА ЗДРАВСТУЕТ АНАРХИЯ, ДА ЧЕРТ ЕГО ПОБЕРИ!
Код:
Hero = { -- типа родительский класс/таблица
  name = "name",
  level = 1,
  myPrint = function(me)
   print ( "I'm ".. me.name ..", my lvl is ".. tostring(me.level) )
  end
}
local tide = {} -- типа наследник
local axe = {} -- типа еще один

setmetatable(tide, {__index = Hero})
setmetatable(axe, {__index = Hero})

function tide:myPrint()
{
	print("TIDE IS HERE, MUTHURFUCKERS! AND MY LEVEL IS 999!")
}

axe.level = 10
axe.name = "Axe"
tide.level = 25
tide.name = "Tide"

print( Hero:myPrint() )
print( tide:myPrint() )
print( axe:myPrint() )
-- I'm name, my lvl is 1
-- TIDE IS HERE, MUTHURFUCKERS! AND MY LEVEL IS 999!
-- I'm Axe, my lvl is 10
Вот для потомка tide мы и переопределили функцию myPrint(). Полезно когда в громоздком классе какая то функция работает почти как надо, но напильинком надо ее все же доработать.

Такс, все основные концепции ООП на lua я показал, теперь предупреждение которое должно было быть в начале.
ПОЛЬЗУЙТЕСЬ ООП НА СВОЙ СТРАХ И РИСК. Вы можете облегчить себе работу, а можете и засрать так что ваш мод будет грузится на топовых компах минут по 30, так что вам оно нужно? ООП чертовских хорош в некоторых местах, но писать чисто используя его, это как чистить зубы ножиком. Да можно, да эффективно, НО ЧЕРТОВСКИ НЕУДОБНО И ДОЛГО.

И под конец, что можно почитать:
ООП для чайников: http://avolberg.ru/theory/oop
Удобная реализация ООП в lua: http://habrahabr.ru/post/259265/
ООП или может ненадо: http://habrahabr.ru/post/147927/
 
Последнее редактирование модератором:

doter.ua

Продвинутый
17 Авг 2014
280
5
Да можно, да эффективно, НО ЧЕРТОВСКИ НЕУДОБНО И ДОЛГО.
1) ООП намного удобнее и компактнее процедурного быдлокода.
2) Инкапсуляцию и полиморфизм можно было не объяснять (в модах рили юзлес).
3) Теория про ООП в целом (вне луа) тоже лишняя. Есть много статей, где это описано намного доступнее, достаточно оставить ссылки на некоторые из них.
4) Вывод в конце ужасный, выглядит так: "Забудьте все, что я вам рассказывал выше, т.к. это велосипед, который испортит ваш код". Если бы я не знал ООП, то решил бы, что это бесполезная хрень.
За старание + конечно. Тема для объяснения не простая, но лучше от нее отказаться, если не уверен в том, что пишешь.
 
Последнее редактирование модератором:

CryDeS

Друзья CG
14 Июл 2015
1,210
11
1) ООП намного удобнее и компактнее процедурного быдлокода.
2) Инкапсуляцию и полиморфизм можно было не объяснять (в модах рили юзлес).
3) Теория про ООП в целом (вне луа) тоже лишняя. Есть много статей, где это описано намного доступнее, достаточно оставить ссылки на некоторые из них.
4) Вывод в конце ужасный, выглядит так: "Забудьте все, что я вам рассказывал выше, т.к. это велосипед, который испортит ваш код". Если бы я не знал ООП, то решил бы, что это бесполезная хрень.
За старание + конечно. Тема для объяснения не простая, но лучше от нее отказаться, если не уверен в том, что пишешь.
ООП удобнее, но не всегда.
Инкапсуляция ненужна, а вот полиморфизм лично я использовал пару раз. Думаю я не очень сложно объяснил теорию. А вывод в конце просто предупреждает что ООП не всегда есть гуд, иногда оно приводит к тому что ради одного банального действия которое не придется никогда больше делать придется написать около 100 строк, когда можно обойтись в 2-3.
 
Последнее редактирование модератором:
Реклама: