[Из песочницы] 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 ())

© Habrahabr.ru