Перейти к содержимому
Ktlo

Другой способ реализации Объектно-Ориентированного Программирования

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

Я видел статью про ООП (Объектно-Ориентированного Программирования), написанную на этом форуме, и вот, что могу сказать, он может быть немного непонятен новичкам в Lua. Я всегда пользовался другим способом создания "классов".

Сначала следует разобраться со значениями. Класс - это набор методов (в нашем случае функций), процедур и переменных которые дальше будут наследоваться объектом или другим классом. Объектами называют сущности, обладающие набором свойств и операций над ними. Объект - это производное класса.

 

В Lua классов как таковых нет, но если пофантазировать, то можно представить функцию как класс, а объект как таблицу.

 

Создание простого псевдо-класса

 

 

1. И так создаем новую функцию и называем её к примеру class.

function class() --Объявление класса
	--Тело класса
end --Конец класса

Пока это просто пустая функция.

 

2.Теперь пропишем в теле класса следующее:

function class() --Объявление класса
	local Me = { } --Создание объекта
	return Me --Возвращение объекта (производного класса)
end --Конец класса

Теперь мы создали пустую таблицу и заставили функцию вернуть её. Для создания полноценного объекта нам не хватает заполнить его свойствами и методами. Таблица будущего объекта должна быть локальной.

 

3.Создадим свойства и методы к нему:

function class() --Объявление класса
	local Me = {  --Таблица для методов и переменных
		["Var"] = "HelloWorld"; --Публичная переменная
	}
	local Secret = "Cake = Lie"  --Скрытая переменная
	function Me.PrintVar()
		print(Me.Var)
	end
	function Me.PrintSecret()
		print(Secret)
	end
	return Me --Возвращение объекта (производного класса)
end --Конец класса

Мы создали два свойства одно скрытое (Secret), а второе публичное (Me.Var) и два метода один для вывода на экран свойства Me.Var, а второй для вывода свойства Secret. Скрытые свойства обязательно должны быть локальными, а публичные находиться в таблице объекта. Также вы можете создать локальную функцию, которую смогут использовать только методы этого объекта.

 

5.Вы можете хранить этот класс в отдельном файле и вызывать его с помощью функции require(), таким образом, чувствовать себя программистом на C, где обычно каждый класс находится в отдельном файле ;) . Выглядит это примерно так:

local class = require("имя_библиотеки").class -- загрузили класс из другого файла (если его нет в этом)
local Object = class() -- Создание объекта на основе класса
Object.PrintVar() --Вызов первого метода
--> HelloWorld

Object.PrintSecret() --Вызов второго метода
--> Cake = Lie

Object.Var = "BuyWorld" --Изменение значения свойства Var
Object.PrintVar() --Вызов первого метода
--> BuyWorld

Здесь же я вызвал все методы объекта, поменял значение свойства Var и снова вызвал. Попробуйте сами, всё должно работать и с несколькими объектами тоже.

 

 


 

Наследование

 

 

Сначала необходимо познакомиться с самим термином. Наследование - это перенимание всех свойств и методов одного класса другим.

 

Давайте напишем новый класс и назовём его parent.

function parent() --Класс предок для будущего класса наследника
	local Me = { }
        local private = {
            ["Secret"] = 42
        }
	Me.Var = "HelloWorld"
	function Me.PrintVar()
		print(Me.Var)
	end
	return Me
end 

В этом классе есть свойство Var и метод PrintVar(), а также скрытое свойство в таблице private.

 

Теперь пишем класс наследник:

function class()
	local Me = parent() --Перенимаем таблицу объекта из класса parent(), то есть наследуем.
	return Me
end 

Всё! Поздравляю, вы наследовали класс! :D  Но на самом деле не полностью, т. к. остаётся наше скрытое свойство. Я не просто так засунул его в таблицу. Мы можем передать эту таблицу как и сам объект, но отдельно.

function parent()--Класс предок
	local Me = {
		["Var"] = "HelloWorld" --Публичное свойство
	}
	local private = {
		["Secret"] = 42 --Скрытое свойство
	}
	function Me.PrintVar() --Метод
		print(Me.Var)
	end
	return Me, private --Возвращение объекта, а затем таблицы со скрытыми переменными
end
function class() --Класс наследник
	local Me, private = parent() --Наследование
	function Me.getVar() --Функция, возвращающая свойство Var из класса parent
		return Me.Var
	end
	function Me.getSecret()
		return private.Secret --Функция, возвращающая скрытое свойство Secret класса parent
	end
	return Me, private --Этот класс можно наследовать тоже, таким образом, получится двойное наследование
end 

Теперь тестим:

local class = require("filename").class --Нужно, если вы разместили класс в другом файле
--[[
Если вы разместили классы в разных файлах, то следует загружать каждый отдельно, но так как они у меня в одном файле, я вызову только наследника, и это всё равно будет работать.
]]

local object = class() --Создаём объект
object.PrintVar() --Тестим метод из класса предка
--> HelloWorld

print(object.getVar()) --Тестим метод класса class, работающий с Var
--> HelloWorld

print(Me.getSecret()) --Получаем значение скрытого свойства
--> 42

Как видите, всё должно работать. Вы можете сами попробовать построить этот класс и вызывать его методы.

В принципе тут больше нечего объяснять, я думаю. Если что-то не понятно, спрашивайте, отвечу.

 

 


 

Экономия оперативной памяти

 

 

Представим класс:

function class()
    return {
        ["foo"] = function()
            --Здесь очень много кода, вычислений, инструкций и т.д.
        end;
        ["var"] = "какая-то переменная, для примера"
    }
    --Оформил тело объекта так, для разнообразия
end 

Главный минус таких "классов" в том, что они тратят оперативную память, бессмысленно создавая идентичные функции, которые сохраняются отдельно. Но этого можно избежать почти полностью. Почему почти, вы сейчас уведите.

Представим класс в несколько ином виде, как блок do <тело класса> end.

do --Начало класса
    local function class_foo(self) --Наша "тяжёлая" функция
        --Здесь очень много кода, вычислений, инструкций и т.д.
    end
    function class() --Функция для вызова класса
    return {
        ["foo"] = function()
            return class_foo(self) --Ссылаемся на локальную функцию
        end;
        ["var"] = "какая-то переменная, для примера"
    }

    end end --Конец класса 

Вот и всё! Сложная функция записывается только один раз, а методы ссылаются на неё. Также вы можете не использовать блок do, но тогда функции будут доступны вне класса.

 

 


 

Только читаемые свойства

 

 

Это последнее, о чём стоит написать в этой теме. Только читаемые свойства могут понадобится для возвращения результатов объекта не используя функции, и при этом, не предоставляя возможность редактировать эти самые свойства. Это можно сделать используя магию метатаблиц. Не знаете, что это? Тогда бегом читать! Хотя сейчас можно обойтись и без этого  ;). Объяснить на словах, как это сделать, трудно, так что просто смотрите код:

function class()
    local private = { } --Таблица со скрытыми свойствами
    private.__index = { --Таблица внутри таблицы скрытых свойств с читаемыми переменными. !!!Важно! Таблица должна сохраняться под ключом __index!!!
        ReadOnly = true; --Читаемое свойство
        Set = function( var ) --Читаемый метод, который изменяет значение читаемого свойства
            private.__index.ReadOnly = var
        end;
    }
    private.secret = 42823 --Скрытое свойство
    private.__newindex = function() end --Метаметод, непозволяющий менять значение таблицы объекта, если приравняете не к функции, а к таблице будет выводить в неё все попытки замены свойств.
    local Me = { } --Сам объект, да он пуст
    setmetatable( Me, private ) --Магия метатаблиц: устанавливает метатаблицу private для объекта
    return Me, private --Зачем я возвращаю таблицу со скрытыми свойствами, смотрите в наследовании
end 

Надеюсь комментарии к коду выше вполне понятны и всё объясняют. Так что мы можем перейти к тестированию.

Тестируем:

local class = require 'class'.class --Загрузка класса, если он в другом файле.

local object = class() --Получаем объект на основе нашего класса
object.Set( "test" ) --Устанавливаем значение для читаемого свойства читаемой функцией. Напрямую мы это сделать не сможем.
print(object.ReadOnly) --Выводим значение ReadOnly
--> test

object.ReadOnly = "He he!"
--Изменяем значение, надеясь, что это сработает

print(object.ReadOnly) --Но он выводит старое значение
--> test

На этом всё. Теперь вы можете использовать читаемые свойства для того, чтобы не создавать специальные для этого методы.

 

 

 

Вывод. Этот способ реализации ООП в Lua будет работать и в OC и в СС. "Классы" очень похожи на классы из других языков программирования. В моём варианте нет двоеточия, которое приносит странную возможность обработки одного объекта методом другого. Использовать ООП в игре можно для удобной разметки интерфейса в вашей программе (например для создания кнопок), и для экономии системных ресурсов в вашем компьютере внутри компьютера.

Изменено пользователем Ktlo

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


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

Обычно натыкаюсь на реализацию классов в виде таблиц. Теперь наконец-то смогу избавиться от двоеточия при вызове функций.

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


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

И зачем так сложно? Не проще либу стандартную написать? и вызывать также require("liba")

local API = {}

 

function API.hello()

 print("Hello!")

end

 

API.var1 = 12345324

API.var2 = true

API.var3 = "aasdp"

...

...

...

return API

Изменено пользователем artem211

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


Ссылка на сообщение
Поделиться на других сайтах
И зачем так сложно? Не проще либу стандартную написать? и вызывать также require("liba")

local API = {}

 

function API.hello()

 print("Hello!")

end

 

API.var1 = 12345324

API.var2 = true

API.var3 = "aasdp"

...

...

...

return API

Понты, понты.

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


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

Ну вот, другое дело. Вот это уже "правильный" "ооп" на луа. good.gif

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


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

И зачем так сложно? Не проще либу стандартную написать? и вызывать также require("liba")

local API = {}

 

function API.hello()

 print("Hello!")

end

 

API.var1 = 12345324

API.var2 = true

API.var3 = "aasdp"

...

...

...

return API

В твоем случае либа всегда будет возвращать указатель на один и тот же объект

Obj1=require("liba")
Obj2=require("liba")

Obj1.var1=54321
print(Obj2.var1)
-->54321

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

В случае же, который предложил Ktlo, создаются новые переменные для полей нового объекта и новые функции для методов.

 

 

Я видел статью про ООП (Объектно-Ориентированного Программирования), написанную на этом форуме, и вот, что могу сказать, он может быть немного непонятен новичкам в Lua. Я всегда пользовался другим способом создания "классов".

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

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

С нетерпением жду описания наследования.

 

PS: далось вам это двоеточие. Вы гоняетесь за внешней похожестью, а ООП на самом деле это не синтаксис компилятора, а уровень мышления программиста.

Изменено пользователем Zer0Galaxy

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


Ссылка на сообщение
Поделиться на других сайтах
94НН03 С006Щ3НN3 П0К4ЗЫ8437, К4КN3 У9N8N73ЛЬНЫ3 83ЩN М0Ж37 93Л47Ь Н4Ш Р4ЗУМ! 8П3Ч47ЛЯЮЩN3 83ЩN! СН4Ч4Л4 Э70 6ЫЛ0 7РУ9Н0, Н0 С3ЙЧ4С Н4 Э70Й С7Р0К3 84Ш Р4ЗУМ ЧN7437 Э70 4870М47NЧ3СКN, Н3 З49УМЫ84ЯСЬ 06 Э70М. Г0Р9NСЬ. ЛNШЬ 0ПР393Л3ННЫ3 ЛЮ9N М0ГУ7 ПР0ЧN747Ь Э70.

Любой программист это прочитает.

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


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

В твоем случае либа всегда будет возвращать указатель на один и тот же объект

Obj1=require("liba")
Obj2=require("liba")

Obj1.var1=54321
print(Obj2.var1)
-->54321

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

В случае же, который предложил Ktlo, создаются новые переменные для полей нового объекта и новые функции для методов.

 

 

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

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

С нетерпением жду описания наследования.

 

PS: далось вам это двоеточие. Вы гоняетесь за внешней похожестью, а ООП на самом деле это не синтаксис компилятора, а уровень мышления программиста.

а почему бы не вызвать функцию прямо из класса? или после инициализции удалять объект импортированной функции...

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


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

а почему бы не вызвать функцию прямо из класса? или после инициализции удалять объект импортированной функции...

Приведи пример, пожалуйста. А то не понятно какую функцию из какого класса? Зачем удалять какой объект?

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


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

 

 

Приведи пример, пожалуйста. А то не понятно какую функцию из какого класса? Зачем удалять какой объект?
 

 

local class = require("имя_библиотеки").class

Эту переменную надо очистить после создания всех экземпляров класса

Либо, дергать так:

require("имя_библиотеки").class()

Изменено пользователем SergOmarov

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


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

Нет. Ты не путай функцию библиотеки и функцию класса.

local class = require("имя_библиотеки").class

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

Способ же, который упомянул топикстартер, действительно создает объект. Но для каждого экземпляра объекта такой способ создает новые экземпляры функций-методов, а функции могут занимать в памяти килобайты.

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


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

Если нужно наследование, можно сделать простейший класс, но с двоеточием (не знаю что вы его пугаетесь (?)):

local class = {a,b}   -- классом может послужить обычная таблица
function class:method(a,b) -- это метод класса, например, пусть меняет значения 
   self.a = a.            -- self указывает на нашу таблицу "class", это синтаксический сахар из-за двоеточия.  
   self.b = b
end
function class:new() -- этим методом мы будем создавать новый объект. Вот тут и начинается магия наследования. 
   local answ = {}  -- таблица которую в конце функции мы возвратим.
   self.__index = self -- теперь при обращении к методом класса будут обращаться именно к его методам. (Сам не до конца понял)
   setmetatable(answ, self) --  магия, указываем метатаблицу answ к классу class 
   return answ  -- возвращаем результат.
end
Сдалось вам это двоеточие. Изменено пользователем LeshaInc

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


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

 

 

Если нужно наследование, можно сделать простейший класс, но с двоеточием (не знаю что вы его пугаетесь (?)):
Хм. Тема называется "ДРУГОЙ способ реализации ...". То о чем пишешь ты это, надо полагать, способ первый.

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


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

Нет. Ты не путай функцию библиотеки и функцию класса.

local class = require("имя_библиотеки").class

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

Способ же, который упомянул топикстартер, действительно создает объект. Но для каждого экземпляра объекта такой способ создает новые экземпляры функций-методов, а функции могут занимать в памяти килобайты.

А, вот ты о чем, а я не понял) Да, на счет этого ты прав, но ведь функции можно хранить и внешним образом, просто вне объекта Me. А ссылаться так как показано в "сложном способе" ооп.

Изменено пользователем SergOmarov

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


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

Всё я закончил с ООП, если есть вопросы, спрашивайте.

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


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

 

 

В моём варианте нет двоеточия, которое приносит странную возможность обработки одного объекта методом другого.
Это как? Так что ли?
Obj1:Metod1()     --Вызов метода объекта 1 (нормальный)
Obj2:Metod2()     --Вызов метода объекта 2 (нормальный)
Obj1.Metod1(Obj2) --Вызов метода объекта 1 для объекта 2 (странный) 

Был несколько разочарован реализацией readonly-полей. Я надеялся на:

print(Obj.ReadOnlyVar)  -- читаем поле
--> 123
Obj.ReadOnlyVar=456     -- пытаемся изменить
print(Obj.ReadOnlyVar)  -- повторно читаем поле
--> 123

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


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

Это как? Так что ли?

Obj1:Metod1()     --Вызов метода объекта 1 (нормальный)
Obj2:Metod2()     --Вызов метода объекта 2 (нормальный)
Obj1.Metod1(Obj2) --Вызов метода объекта 1 для объекта 2 (странный) 

Да, так.

 

Был несколько разочарован реализацией readonly-полей. Я надеялся на:

print(Obj.ReadOnlyVar)  -- читаем поле
--> 123
Obj.ReadOnlyVar=456     -- пытаемся изменить
print(Obj.ReadOnlyVar)  -- повторно читаем поле
--> 123

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

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


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

Можно)

переопределить оператор присваивания

Как?

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


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

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в тему...

×   Вы вставили отформатированное содержимое.   Удалить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.


×
×
  • Создать...