Классы в lua, или избавляемся от двоеточия

от автора

Как всем известно, в lua нет как таковых классов и объектов. Однако есть метатаблицы и синтаксический сахар.
С помощью указанных механизмов достаточно просто реализовать подобие классов.
В итоге и получается нечто такое:

Самый простой класс

local MyClass = {} -- the table representing the class, which will double as the metatable for the instances MyClass.__index = MyClass -- failed table lookups on the instances should fallback to the class table, to get methods  -- syntax equivalent to "MyClass.new = function..." function MyClass.new(init)   local self = setmetatable({}, MyClass)   self.value = init   return self end  function MyClass.set_value(self, newval)   self.value = newval end  function MyClass.get_value(self)   return self.value end  local i = MyClass.new(5) -- tbl:name(arg) is a shortcut for tbl.name(tbl, arg), except tbl is evaluated only once print(i:get_value()) --> 5 i:set_value(6) print(i:get_value()) --> 6 

(взято с lua-users.org/wiki/ObjectOrientationTutorial)

Всё это конечно хорошо, даже при определённой сноровке можно реализовать наследование…
Но где public и private члены класса? Дефакто в этом примере они все public. Да ещё и надо помнить, где использовать двоеточие:

MyClass:myFunc() 

а где просто одну точку:

MyClass.myOtherFunc() 

А статические члены класса? Неужели придётся отказываться?

Вот я и не захотел отказываться, и начал колхозить…

Итак, представляю вам мой колхоз:

Мой колхоз

createClass = function() 	local creator = {} 	creator.__private = { 		object_class = {}, 	} 	 	creator.__oncall = function(class_creator)		 		-- Get the class definition so we can make needed variables private, static, etc. 		local this = class_creator() 		-- Initialize class from class definition 		__init = function() 			-- Init Public Static 			local class = {} 			if (type(this.__public_static) == "table") then 				class = this.__public_static 			end 			 			-- Init Object 			local thisClass = this 			local __constructor = function(...) 				local object = {} 				local this = class_creator()  				-- Init Public 				if (type(this.__public) == "table") then 					object = this.__public 				end  				-- Init static values of the class 				this.__public_static = thisClass.__public_static 				this.__private_static = thisClass.__private_static  				-- Call Constructor 				if (type(this.__construct) == "function") then 					this.__construct(...) 				end 				 				-- Returning constructed object 				return object 			end 			return {class = class, constructor = __constructor} 		end  		-- Creating class (returning constructor) 		local class_data = __init() 		local class = class_data.class 		local constructor = class_data.constructor  		-- Set class metatable (with setting constructor) 		local class_metatable = { 			__newindex = function(t, key, value) 				if type(t[key])=="nil" or type(t[key])=="function" then 					error("Attempt to redefine class") 				end 				rawset(t, key, value) 			end, 			__metatable = false, 			__call = function(t, ...) 				if type(t) == nil then 					error("Class object create failed!") 				end 				local obj = constructor(...) 				creator.__private.object_class[obj] = t 				local object_metatable = { 					__newindex = function(t, key, value) 						class = creator.__private.object_class[t] 						if type(class[key])~="nil" and type(class[key])~="function" then 							rawset(class, key, value) 							return 						end 						if type(t[key])~="nil" and type(t[key])~="function" then 							rawset(t, key, value) 							return 						end 						error("Attempt to redefine object") 					end, 					__index = t, 					__metatable = false, 				} 				setmetatable(obj, object_metatable) 				return obj 			end, 		}  		-- Setting class metatable to the class itself 		setmetatable(class, class_metatable)  		-- Returning resulting class 		return class 	end 	 	return creator.__oncall end createClass = createClass() 

А пользоваться как? Очень просто, вот вам шаблон:

myclass_prototype = function() 	local this = {} 	this.__public_static = { 		-- Public Static Variables 		statvalue = 5, 		-- Public Static Funcs 		statfunc = function() 			print(this.__public_static.statvalue) 		end, 	}  	this.__private_static = { 		-- Private Static Variables 		privstatdat = 2, 		-- Private Static Funcs 		privstatfunc = function() 			print(this.__private_static.privstatdat) 		end, 	}  	this.__public = { 		-- Public Variables 		pubdata = 3, 		-- Public Funcs 		pubfunc  = function(newprivate) 			print(this.__public.pubdata) 			this.__private.privdata = newprivate 		end, 	}  	this.__private = { 		-- Private Variables 		privdata = 1, 		-- Private Funcs 		listallprivate = function() 			print(this.__private.privdata) 		end, 	}  	this.__construct = function() 		-- Constructor 	end  	return this end  myclass=createClass(myclass_prototype) 

Как видите, при каждом вызове изнутри класса придётся каждый раз указывать путь, а ля «this.__private.privdata», зато вот вам пример использования созданного класса!

myobject = myclass() myobject.pubfunc(999) 

При вызове этого кода будет создан объект myobject из класса myclass, и будет вызвана функция pubfunc, которая высветит содержимое публичной переменной и изменит приватную.
И никаких заморочек с двоеточиями!
Кстати, статические вызовы тоже работают. Как из класса, так и из объекта.

Итак, вкратце расскажу, что за магия здесь происходит. А происходит тут жонглирование так называемыми upvalues. upvalues — это переменные, которые изнутри видны, а снаружи — нет! Очень похоже на private, не так ли?
Так вот, создав функцию-«прототип», мы создали новую область видимости, и в неё поместили все внутренности нашего класса, вынеся наружу только public и public static члены класса. А всю остальную магию выполняют метатаблицы, которые позволяют определить, что именно будет происходить при запросе «несуществующего» члена внешней таблицы, которая представляет наш класс/объект.
Сумбурно звучит, знаю, но лучше не могу объяснить — не спец 🙂

Долго думал, как можно сделать наследование при такой системе, но так и не придумал — upvalues достаточно серьёзно ограничивает наши действия, а извращенствами вроде debug библиотеки пользоваться не хотелось — она не везде включена. Если кто додумается, буду рад увидеть!

PS: если для кого-то мой пост нечто очевидное — чтож, значит я себя переоценил 🙂 Не судите строго, может кому-то зачем-то пригодится это решение!

ссылка на оригинал статьи http://habrahabr.ru/post/182018/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *