[Из песочницы] Lua, ООП и ничего лишнего
Однажды судьба свела меня с ней. С первого взгляда я был ослеплен и долгое время не мог отвести от нее взгляд. Шло время, но она не переставала меня удивлять, иногда казалось, что я изучил ее вдоль и поперек, но она снова переворачивала все мои представления. Ее гибкости не было предела, а потом я узнал, что она умеет еще и… ООП! Как-то я всерьез занялся покорением ООП в lua. И все, что я находил в интернете по этой теме, было вырвиглазными нагромождениями кода с обилием нижних подчеркиваний, которые никак не вписывались в элегантность этого языка. Поэтому я решил искать простое решение.
После прочтения множества умных книжек и разбора нескольких ужасных реализаций ООП, я, крупица за крупицей, собирал все самое полезное и простое, пока не выработал свой стиль объектно ориентированного программирования на lua.
Создание класса и экземпляраclass Person --класс Person= {} --тело класса function Person: new (fName, lName)
— свойства local obj= {} obj.firstName = fName obj.lastName = lName
— метод function obj: getName () return self.firstName end
--чистая магия! setmetatable (obj, self) self.__index = self; return obj end
--создаем экземпляр класса vasya = Person: new («Вася», «Пупкин»)
--обращаемся к свойству print (vasya.firstName)
--обращаемся к методу print (vasya: getName ()) Как видите, все очень просто. Если кто-то путается где ставить точку, а где двоеточие, правило следующее: если обращаемся к свойству — ставим точку (obj.name), если к методу — ставим двоеточие (obj: getName ()).Дальше интереснее.
Как известно, ООП держится на трех китах: наследование, инкапсуляция и полиморфизм. Проведем «разбор полетов» в этом же порядке.
Наследование Допустим, нам нужно создать класс унаследованный от предыдущего (Person).class Woman Woman = {} --наследуемся setmetatable (Woman,{__index = Person}) --проверяем masha = Woman: new («Марья», «Ивановна») print (masha: getName ()) --->результат: Марья Все работает, но лично мне не нравится такой вариант наследования, некрасиво. Поэтому я просто создаю глобальную функцию extended (): extended () function extended (child, parent) setmetatable (child,{__index = parent}) end Теперь наследование классов выглядит куда красивее: class Woman Woman = {}; --наследуемся extended (Woman, Person) --проверяем masha = Woman: new («Марья», «Ивановна») print (masha: getName ()) --->результат: Марья Инкапсуляция Все свойства и методы до этого момента в наших классах были публичные, но мы так же легко можем создавать и приватные: class Person Person = {} function Person: new (name) private = {} --приватное свойство private.name = name or «Вася» — «Вася» — значение по умолчанию
local public = {} --публичное свойство public.age = 18 --публичный метод function public: getName () return private.name end
setmetatable (public, self) self.__index = self; return public end
vasya = Person: new ()
print (vasya.name) --> результат: nil
print (vasya.age) --> результат: 18
print (vasya: getName ()) --> результат: Вася Видите? Все почти так же как вы и привыкли.Полиморфизм Тут все еще проще. Только есть одно но! Методы, объявленные в теле «класса», нельзя переопределять.полиморфизм Person = {} function Person: new (name) private = {} private.name = name or «Вася»
local public = {} public.age = 18
function public: getName () return private.name end
setmetatable (public, self) self.__index = self; return public end
function Person: setName (name) private.name = name end
--создадим класс, унаследованный от Person Woman = {} extended (Woman, Person) --не забываем про эту функцию
--переопределим метод setName function Woman: setName (name) private.name = «переопределенная »…name end
--проверим masha = Woman: new () print (masha: getName ()) --> Вася
masha: setName («Света») print (masha: getName ()) --> переопределенная Света Итак, что мы тут сделали? — создали класс Person, с двумя методами: getName () и setName (), первый из них защищен он переопределения (т.к объявлен в теле класса); — создали класс Woman и унаследовали его от класса Person; — переопределили метод setName () в классе Woman; — получили профит! А что делать, если нужно вызвать метод базового класса, который у нас переопределен? Это тоже делается легко! Синтаксис таков: РодительскийКласс.Метод (сам_объект, параметры).
class Woman --создадим класс, унаследованный от Person Woman = {} extended (Woman, Person) --не забываем про эту функцию
--переопределим метод setName function Woman: setName (name) private.name = «переопределенная »…name end
masha: setName («Света») print (masha: getName ()) --> переопределенная Света
--вызываем метод родительского класса Person.setName (masha, «Света») print (masha: getName ()) --> Света Постскриптум На этом все, искренне надеюсь, что хоть кому-нибудь эта статья окажется полезной.Напоследок приведу полный код всего вышесказанного, можете его скопипастить в IDE и убедиться в работоспособности.
Полный код function extended (child, parent) setmetatable (child,{__index = parent}) end
Person = {} function Person: new (name) private = {} private.name = name or «Вася»
local public = {} public.age = 18
function public: getName () return private.name end
setmetatable (public, self) self.__index = self; return public end
function Person: setName (name) private.name = name end
vasya = Person: new () vasya: setName («Петя») print (vasya: getName ()) --> Петя print (vasya.age) --> 18
--создадим класс, унаследованный от Person Woman = {} extended (Woman, Person)
--переопределим метод setName function Woman: setName (name) private.name = «переопределенная »…name end
masha = Woman: new () print (masha: getName ()) --> Вася
masha: setName («Света») print (masha: getName ()) --> переопределенная Света
Person.setName (masha, «Света») print (masha: getName ())