imported>Oetterer |
|||
Zeile 9: | Zeile 9: | ||
== Quick Example == | == Quick Example == | ||
− | < | + | <syntaxhighlight lang="lua"> |
local class = require('Module:Middleclass').class | local class = require('Module:Middleclass').class | ||
Zeile 43: | Zeile 43: | ||
mw.log(p1:speak()) | mw.log(p1:speak()) | ||
mw.log(p2:speak()) | mw.log(p2:speak()) | ||
− | </ | + | </syntaxhighlight> |
Output: | Output: | ||
Zeile 58: | Zeile 58: | ||
You can compare classes and objects with the built-in methods <code>isInstanceOf</code>, <code>isSubclassOf</code>, and <code>includes</code>. | You can compare classes and objects with the built-in methods <code>isInstanceOf</code>, <code>isSubclassOf</code>, and <code>includes</code>. | ||
− | < | + | <syntaxhighlight lang="lua"> |
obj:isInstanceOf(MyClass) | obj:isInstanceOf(MyClass) | ||
aClass:isSubclassOf(Object) | aClass:isSubclassOf(Object) | ||
aClass:includes(aMixin) | aClass:includes(aMixin) | ||
− | </ | + | </syntaxhighlight> |
Caution: this code will throw an error if obj is not an object, or if aClass is not a class (since they will not implement isInstanceOf, isSubclassOf or includes). If you are unsure of whether obj and aClass are an object or a class, you can use the methods in Object. This is a special object that can be obtained from the class object. | Caution: this code will throw an error if obj is not an object, or if aClass is not a class (since they will not implement isInstanceOf, isSubclassOf or includes). If you are unsure of whether obj and aClass are an object or a class, you can use the methods in Object. This is a special object that can be obtained from the class object. | ||
Zeile 68: | Zeile 68: | ||
It is possible to get a special object ''Object'' from the class function. | It is possible to get a special object ''Object'' from the class function. | ||
− | < | + | <syntaxhighlight lang="lua"> |
local middleclass = require('Module:Middleclass') | local middleclass = require('Module:Middleclass') | ||
local class = middleclass.class | local class = middleclass.class | ||
Zeile 74: | Zeile 74: | ||
print(Object) -- prints 'class Object' | print(Object) -- prints 'class Object' | ||
− | </ | + | </syntaxhighlight> |
The methods in Object are prepared to work with random types, not just classes and instances: | The methods in Object are prepared to work with random types, not just classes and instances: | ||
− | < | + | <syntaxhighlight lang="lua"> |
Object.isInstanceOf(obj, MyClass) | Object.isInstanceOf(obj, MyClass) | ||
Object.isSubclassOf(aClass, Object) | Object.isSubclassOf(aClass, Object) | ||
Object.includes(aClass, aMixin) | Object.includes(aClass, aMixin) | ||
− | </ | + | </syntaxhighlight> |
== Metamethods == | == Metamethods == | ||
Zeile 90: | Zeile 90: | ||
Let’s make an example with <code>__tostring</code> | Let’s make an example with <code>__tostring</code> | ||
− | < | + | <syntaxhighlight lang="lua"> |
Point = class('Point') | Point = class('Point') | ||
Zeile 106: | Zeile 106: | ||
mw.log(p1) | mw.log(p1) | ||
mw.log(p2) | mw.log(p2) | ||
− | </ | + | </syntaxhighlight> |
Output: | Output: | ||
Zeile 117: | Zeile 117: | ||
The complete list of supported methods can be seen on middleclass’ source code: | The complete list of supported methods can be seen on middleclass’ source code: | ||
− | < | + | <syntaxhighlight lang="lua"> |
'__add', '__call', '__concat', '__div', '__le', '__lt', '__mod', '__mul', '__pow', '__sub', '__tostring', '__unm' | '__add', '__call', '__concat', '__div', '__le', '__lt', '__mod', '__mul', '__pow', '__sub', '__tostring', '__unm' | ||
− | </ | + | </syntaxhighlight> |
You may notice that <code>__index</code> is missing. That metamethod is reserved for the lib and can’t be used. On middleclass 1.x there was a way around this (via a mixin) but for now I don’t recommend using this metamethod with classes. You will probably be better off using a pure Lua table instead, if you need that. | You may notice that <code>__index</code> is missing. That metamethod is reserved for the lib and can’t be used. On middleclass 1.x there was a way around this (via a mixin) but for now I don’t recommend using this metamethod with classes. You will probably be better off using a pure Lua table instead, if you need that. | ||
Zeile 129: | Zeile 129: | ||
Mixins can be used for sharing methods between classes, without requiring them to inherit from the same father. | Mixins can be used for sharing methods between classes, without requiring them to inherit from the same father. | ||
− | < | + | <syntaxhighlight lang="lua"> |
local class = require 'middleclass' | local class = require 'middleclass' | ||
HasWings = { -- HasWings is a module, not a class. It can be "included" into classes | HasWings = { -- HasWings is a module, not a class. It can be "included" into classes | ||
Zeile 157: | Zeile 157: | ||
mw.log(bee:fly()) | mw.log(bee:fly()) | ||
mw.log(bat:fly()) | mw.log(bat:fly()) | ||
− | </ | + | </syntaxhighlight> |
Output: | Output: | ||
Zeile 170: | Zeile 170: | ||
Mixins can provide a special function called 'included'. This function will be invoked when the mixin is included on a class, allowing the programmer to do actions. The only two parameters are the mixin (normally implicit) and the class. | Mixins can provide a special function called 'included'. This function will be invoked when the mixin is included on a class, allowing the programmer to do actions. The only two parameters are the mixin (normally implicit) and the class. | ||
− | < | + | <syntaxhighlight lang="lua"> |
local class = require('Module:Middleclass').class | local class = require('Module:Middleclass').class | ||
DrinksCoffee = {} | DrinksCoffee = {} | ||
Zeile 212: | Zeile 212: | ||
mw.log(juan:drink(5)) | mw.log(juan:drink(5)) | ||
mw.log(juan:drink(6)) | mw.log(juan:drink(6)) | ||
− | </ | + | </syntaxhighlight> |
Output: | Output: | ||
Zeile 234: | Zeile 234: | ||
* One exception to this rule is when classes are declared inside packages in that case, they can be declared as follows: | * One exception to this rule is when classes are declared inside packages in that case, they can be declared as follows: | ||
− | < | + | <syntaxhighlight lang="lua"> |
MyClass = class('package.MyClass') | MyClass = class('package.MyClass') | ||
− | </ | + | </syntaxhighlight> |
* Another exception is for internal classes (classed declared inside classes) | * Another exception is for internal classes (classed declared inside classes) | ||
− | < | + | <syntaxhighlight lang="lua"> |
MyClass = class('package.MyClass') | MyClass = class('package.MyClass') | ||
MyClass.InternalClass = class('package.MyClass.InternalClass') | MyClass.InternalClass = class('package.MyClass.InternalClass') | ||
− | </ | + | </syntaxhighlight> |
=== Attributes, instances and constants === | === Attributes, instances and constants === | ||
Zeile 259: | Zeile 259: | ||
* Instance methods should be declared using the colons, so they have an implicit ‘self’ parameter: | * Instance methods should be declared using the colons, so they have an implicit ‘self’ parameter: | ||
− | < | + | <syntaxhighlight lang="lua"> |
function MyClass:setX(x) | function MyClass:setX(x) | ||
self.x = x | self.x = x | ||
end | end | ||
− | </ | + | </syntaxhighlight> |
* Class methods should use the special property ‘static’ for being defined: | * Class methods should use the special property ‘static’ for being defined: | ||
− | < | + | <syntaxhighlight lang="lua"> |
function MyClass.static:classMethod() | function MyClass.static:classMethod() | ||
return 'I am the ' .. self.name .. ' class. I am awesome' | return 'I am the ' .. self.name .. ' class. I am awesome' | ||
end | end | ||
− | </ | + | </syntaxhighlight> |
* Private methods should be preceded with an underscore: <code>_myPrivateMethod</code> | * Private methods should be preceded with an underscore: <code>_myPrivateMethod</code> | ||
Zeile 298: | Zeile 298: | ||
The simplest one is just to precede your attributes with underscores. This is actually written on the Lua 5.1 reference, section 2.1, “Lexical conversions”, as a way to say “this is here, but please don’t use it”. | The simplest one is just to precede your attributes with underscores. This is actually written on the Lua 5.1 reference, section 2.1, “Lexical conversions”, as a way to say “this is here, but please don’t use it”. | ||
− | < | + | <syntaxhighlight lang="lua"> |
local class = require('middleclass') | local class = require('middleclass') | ||
MyClass = class('MyClass') | MyClass = class('MyClass') | ||
Zeile 306: | Zeile 306: | ||
self.publicStuff = 2 | self.publicStuff = 2 | ||
end | end | ||
− | </ | + | </syntaxhighlight> |
However, this isn’t really making the properties “hidden”. | However, this isn’t really making the properties “hidden”. | ||
Zeile 316: | Zeile 316: | ||
Example: | Example: | ||
− | < | + | <syntaxhighlight lang="lua"> |
-- File 'MyClass2.lua' | -- File 'MyClass2.lua' | ||
local class = require('middleclass') | local class = require('middleclass') | ||
Zeile 332: | Zeile 332: | ||
return(_internalClassCounter) | return(_internalClassCounter) | ||
end | end | ||
− | </ | + | </syntaxhighlight> |
The scope of local declarations on a lua file is the file itself. If you declare something “local” in one file it is not available on others, even if they “require” that file. | The scope of local declarations on a lua file is the file itself. If you declare something “local” in one file it is not available on others, even if they “require” that file. | ||
− | < | + | <syntaxhighlight lang="lua"> |
-- File 'main.lua' | -- File 'main.lua' | ||
Zeile 345: | Zeile 345: | ||
mw.log(MyClass2:getCount()) -- prints "0" | mw.log(MyClass2:getCount()) -- prints "0" | ||
− | </ | + | </syntaxhighlight> |
Let me explain what happens here. The _internalClassCounter = 4 line is, in reality, creating a new global variable called internalClassCounter, and assigning it 4. The “really internal” one is “out of reach” on main.lua (unless someone does really tricky stuff with the environments). So getCount() works as expected. | Let me explain what happens here. The _internalClassCounter = 4 line is, in reality, creating a new global variable called internalClassCounter, and assigning it 4. The “really internal” one is “out of reach” on main.lua (unless someone does really tricky stuff with the environments). So getCount() works as expected. | ||
Zeile 353: | Zeile 353: | ||
It is also possible to declare private methods. The trick here is not to “include” them on the class definition. On the following example, we will not declare it on <code>Class3:secretMethod</code>; instead we’ll create a local function. Since we’re not using the : operator any more, we have to make the “self” parameter explicit. Also, since we have to make it local, we have to deviate from the “usual” way of declaring Lua functions (the “usual” way of declaring functions makes them global): | It is also possible to declare private methods. The trick here is not to “include” them on the class definition. On the following example, we will not declare it on <code>Class3:secretMethod</code>; instead we’ll create a local function. Since we’re not using the : operator any more, we have to make the “self” parameter explicit. Also, since we have to make it local, we have to deviate from the “usual” way of declaring Lua functions (the “usual” way of declaring functions makes them global): | ||
− | < | + | <syntaxhighlight lang="lua"> |
-- File 'MyClass3.lua' | -- File 'MyClass3.lua' | ||
local class = require('middleclass') | local class = require('middleclass') | ||
Zeile 370: | Zeile 370: | ||
return _secretMethod(self) .. ' You will never know it!' | return _secretMethod(self) .. ' You will never know it!' | ||
end | end | ||
− | </ | + | </syntaxhighlight> |
− | < | + | <syntaxhighlight lang="lua"> |
-- File 'Main.lua' | -- File 'Main.lua' | ||
require('MyClass3') | require('MyClass3') | ||
Zeile 381: | Zeile 381: | ||
mw.log(_secretMethod(peter)) -- throws an error - _secretMethod is nil here. | mw.log(_secretMethod(peter)) -- throws an error - _secretMethod is nil here. | ||
− | </ | + | </syntaxhighlight> |
This technique also allows the creation of private class methods. In MiddleClass, there’s really no difference between class methods and instance methods; the difference comes from what you pass to their ‘self’ parameter. So if you invoke _secretMethod like this: _secretMethod(MyClass3) it will be a class method. | This technique also allows the creation of private class methods. In MiddleClass, there’s really no difference between class methods and instance methods; the difference comes from what you pass to their ‘self’ parameter. So if you invoke _secretMethod like this: _secretMethod(MyClass3) it will be a class method. | ||
Zeile 387: | Zeile 387: | ||
A slightly more efficient way of creating a class method would be getting rid of the ‘self’ parameter and use MyClass3 directly on the method’s body: | A slightly more efficient way of creating a class method would be getting rid of the ‘self’ parameter and use MyClass3 directly on the method’s body: | ||
− | < | + | <syntaxhighlight lang="lua"> |
MyClass3 = class('MyClass3') | MyClass3 = class('MyClass3') | ||
Zeile 400: | Zeile 400: | ||
return( 'Being a public class named ' .. theClass.name .. ' is not a bad thing.' ) | return( 'Being a public class named ' .. theClass.name .. ' is not a bad thing.' ) | ||
end | end | ||
− | </ | + | </syntaxhighlight> |
This gives a bit more of flexibility when overriding public class methods on subclasses. | This gives a bit more of flexibility when overriding public class methods on subclasses. | ||
Zeile 406: | Zeile 406: | ||
Finally, a subtle point regarding recursive private methods. If you need to create a private method that calls himself, you will need to declare the variable first, and then (on the next line) initialize it with the function value. Otherwise the variable will not be available when the function is created | Finally, a subtle point regarding recursive private methods. If you need to create a private method that calls himself, you will need to declare the variable first, and then (on the next line) initialize it with the function value. Otherwise the variable will not be available when the function is created | ||
− | < | + | <syntaxhighlight lang="lua"> |
MyClass3 = class('MyClass3') | MyClass3 = class('MyClass3') | ||
Zeile 425: | Zeile 425: | ||
m = MyClass3:new() | m = MyClass3:new() | ||
m:recurseOver(5) | m:recurseOver(5) | ||
− | </ | + | </syntaxhighlight> |
Output: | Output: | ||
Zeile 454: | Zeile 454: | ||
-By the way, the following example also shows how you can do “read-only-ish attributes”: you make them private, and make getters for them, but not setters. | -By the way, the following example also shows how you can do “read-only-ish attributes”: you make them private, and make getters for them, but not setters. | ||
− | < | + | <syntaxhighlight lang="lua"> |
-- File 'MyClass4.lua' | -- File 'MyClass4.lua' | ||
local class = require('middleclass') | local class = require('middleclass') | ||
Zeile 481: | Zeile 481: | ||
return _private[self].gender | return _private[self].gender | ||
end | end | ||
− | </ | + | </syntaxhighlight> |
− | < | + | <syntaxhighlight lang="lua"> |
-- File 'main.lua' | -- File 'main.lua' | ||
Zeile 508: | Zeile 508: | ||
mw.log(stewie:getGender()) -- 'male' | mw.log(stewie:getGender()) -- 'male' | ||
mw.log(stewie.gender) -- 'female' | mw.log(stewie.gender) -- 'female' | ||
− | </ | + | </syntaxhighlight> |
=== Private members on the same file === | === Private members on the same file === | ||
Zeile 516: | Zeile 516: | ||
Just create an artificial scope with do … end, and declare private members as ‘local’ inside that block. Only the methods inside that block will have access to them: | Just create an artificial scope with do … end, and declare private members as ‘local’ inside that block. Only the methods inside that block will have access to them: | ||
− | < | + | <syntaxhighlight lang="lua"> |
-- File 'MyClass3.lua' | -- File 'MyClass3.lua' | ||
local class = require('middleclass') | local class = require('middleclass') | ||
Zeile 537: | Zeile 537: | ||
-- functions outside the do-end will not 'see' secretMethod, but they will see MyClass3.shout (because they see MyClass3) | -- functions outside the do-end will not 'see' secretMethod, but they will see MyClass3.shout (because they see MyClass3) | ||
− | </ | + | </syntaxhighlight> |
== MIT License == | == MIT License == | ||
Zeile 554: | Zeile 554: | ||
<!-- Categories go here if not set by {{module rating}} above (in which case you can use default [[Category:modules]]. --> | <!-- Categories go here if not set by {{module rating}} above (in which case you can use default [[Category:modules]]. --> | ||
[[Category:Lua metamodules]] | [[Category:Lua metamodules]] | ||
− | }}</includeonly> | + | }}</includeonly><!-- null edit comment; remove --> |
Aktuelle Version vom 5. Oktober 2022, 15:08 Uhr
This is a documentation subpage for Modul:Middleclass. It contains usage information, categories and other content that is not part of the original modul page. |
This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
Middleclass is an object-oriented library for Lua, written by Enrique García Cota (kikito) and maintained at GitHub.
If you are familiar with Object Orientation in other languages (C++, Java, Ruby … ) then you will probably find this library very easy to use.
The code is reasonably commented, so you might want to employ some time in reading through it, in order to gain insight of Lua’s amazing flexibility.
Quick Example[Quelltext bearbeiten]
local class = require('Module:Middleclass').class
Person = class('Person') --this is the same as class('Person', Object) or Object:subclass('Person')
function Person:initialize(name)
self.name = name
end
function Person:speak()
return 'Hi, I am ' .. self.name ..'.'
end
AgedPerson = class('AgedPerson', Person) -- or Person:subclass('AgedPerson')
AgedPerson.static.ADULT_AGE = 18 --this is a class variable
function AgedPerson:initialize(name, age)
Person.initialize(self, name) -- this calls the parent's constructor (Person.initialize) on self
self.age = age
end
function AgedPerson:speak()
local hi = Person.speak(self) -- "Hi, I am xx."
if self.age < AgedPerson.ADULT_AGE then -- accessing a class variable from an instance method
return hi .. '\nI am underaged.'
else
return hi .. '\nI am an adult.'
end
end
local p1 = AgedPerson:new('Billy the Kid', 13) -- this is equivalent to AgedPerson('Billy the Kid', 13) - the :new part is implicit
local p2 = AgedPerson:new('Luke Skywalker', 21)
mw.log(p1:speak())
mw.log(p2:speak())
Output:
Hi, I'm Billy the Kid. I am underaged. Hi, I'm Luke Skywalker. I am an adult.
Comparing classes and objects[Quelltext bearbeiten]
You can compare classes and objects with the built-in methods isInstanceOf
, isSubclassOf
, and includes
.
obj:isInstanceOf(MyClass)
aClass:isSubclassOf(Object)
aClass:includes(aMixin)
Caution: this code will throw an error if obj is not an object, or if aClass is not a class (since they will not implement isInstanceOf, isSubclassOf or includes). If you are unsure of whether obj and aClass are an object or a class, you can use the methods in Object. This is a special object that can be obtained from the class object.
It is possible to get a special object Object from the class function.
local middleclass = require('Module:Middleclass')
local class = middleclass.class
local Object = class.Object
print(Object) -- prints 'class Object'
The methods in Object are prepared to work with random types, not just classes and instances:
Object.isInstanceOf(obj, MyClass)
Object.isSubclassOf(aClass, Object)
Object.includes(aClass, aMixin)
Metamethods[Quelltext bearbeiten]
Metamethods can do funky stuff like allowing additions in our instances.
Let’s make an example with __tostring
Point = class('Point')
function Point:initialize(x,y)
self.x = x
self.y = y
end
function Point:__tostring()
return 'Point: [' .. tostring(self.x) .. ', ' .. tostring(self.y) .. ']'
end
p1 = Point(100, 200)
p2 = Point(35, -10)
mw.log(p1)
mw.log(p2)
Output:
Point: [100, 200] Point: [35, -10]
The complete list of supported methods can be seen on middleclass’ source code:
'__add', '__call', '__concat', '__div', '__le', '__lt', '__mod', '__mul', '__pow', '__sub', '__tostring', '__unm'
You may notice that __index
is missing. That metamethod is reserved for the lib and can’t be used. On middleclass 1.x there was a way around this (via a mixin) but for now I don’t recommend using this metamethod with classes. You will probably be better off using a pure Lua table instead, if you need that.
Note about Lua 5.2: This new version of Lua adds support for 3 new metamethods __len
, __pairs
& __ipairs
. These methods are now also included in middleclass. But you will need to be running Lua 5.2 (or Luajit compiled with the DLUAJIT_ENABLE_LUA52COMPAT
flag) in order to be able to use them.
Mixins[Quelltext bearbeiten]
Mixins can be used for sharing methods between classes, without requiring them to inherit from the same father.
local class = require 'middleclass'
HasWings = { -- HasWings is a module, not a class. It can be "included" into classes
fly = function(self)
return 'flap flap flap I am a ' .. self.class.name
end
}
Animal = class('Animal')
Insect = class('Insect', Animal) -- or Animal:subclass('Insect')
Worm = class('Worm', Insect) -- worms don't have wings
Bee = class('Bee', Insect)
Bee:include(HasWings) --Bees have wings. This adds fly() to Bee
Mammal = class('Mammal', Animal)
Fox = class('Fox', Mammal) -- foxes don't have wings, but are mammals
Bat = class('Bat', Mammal)
Bat:include(HasWings) --Bats have wings, too.
local bee = Bee() -- or Bee:new()
local bat = Bat() -- or Bat:new()
mw.log(bee:fly())
mw.log(bat:fly())
Output:
flap flap flap I am a Bee flap flap flap I am a Bat
Included[Quelltext bearbeiten]
Mixins can provide a special function called 'included'. This function will be invoked when the mixin is included on a class, allowing the programmer to do actions. The only two parameters are the mixin (normally implicit) and the class.
local class = require('Module:Middleclass').class
DrinksCoffee = {}
-- This is another valid way of declaring functions on a mixin.
-- Note that we are using the : operator, so there's an implicit self parameter
function DrinksCoffee:drink(drinkTime)
if(drinkTime~=self.class.coffeeTime) then
return self.name .. ': It is not the time to drink coffee!'
else
return self.name .. ': Mmm I love coffee at ' .. drinkTime
end
end
-- the included method is invoked every time DrinksCoffee is included on a class
-- notice that paramters can be passed around
function DrinksCoffee:included(klass)
mw.log(klass.name .. ' drinks coffee at ' .. klass.coffeeTime)
end
EnglishMan = class('EnglishMan')
EnglishMan.static.coffeeTime = 5
EnglishMan:include(DrinksCoffee)
function EnglishMan:initialize(name)
self.name = name
end
Spaniard = class('Spaniard')
Spaniard.static.coffeeTime = 6
Spaniard:include(DrinksCoffee)
function Spaniard:initialize(name)
self.name = name
end
tom = EnglishMan:new('tom')
juan = Spaniard:new('juan')
mw.log(tom:drink(5))
mw.log(juan:drink(5))
mw.log(juan:drink(6))
Output:
EnglishMan drinks coffee at 5 Spaniard drinks coffee at 6 tom: Mmm I love coffee at 5 juan: It is not the time to drink coffee! juan: Mmm I love coffee at 6
Naming Conventions[Quelltext bearbeiten]
What follows is a set of recommendations used for naming objects with MiddleClass. These are completely optional, but will be used in all the modules directly dependent on MiddleClass (such as middleclass-extras or PÄSSION)
Classes and packages[Quelltext bearbeiten]
- Package names should be lowercased and separated by underscores: package_name
- Class names & mixin names should begin with Uppercase, and use camelCase notation: MyClass, MyMixin
- One exception to this rule is when classes are declared inside packages in that case, they can be declared as follows:
MyClass = class('package.MyClass')
- Another exception is for internal classes (classed declared inside classes)
MyClass = class('package.MyClass')
MyClass.InternalClass = class('package.MyClass.InternalClass')
Attributes, instances and constants[Quelltext bearbeiten]
- Attributes begin with lowercase, and use camelCase: MyClass.attributeOne, MyClass.attributeTwo
- An underscore can precede the initial lowercase if the attribute is supposed to be private: MyClass._privateAttribute
- Variables pointing to instances of classes should start with lowercase and be camelCased: myInstance = MyClass:new()
- Constants should be ALL_UPPERCASED, and use underscores for word separations: MyClass.MY_CONSTANT
- Private attributes should be preceded with an underscore: _myPrivateAttribute
Methods[Quelltext bearbeiten]
- Methods should begin with lowercase, and use camelCase: MyClass.myMethod
- When possible, methods should use explicit verbs: getX() instead of x()
- Instance methods should be declared using the colons, so they have an implicit ‘self’ parameter:
function MyClass:setX(x)
self.x = x
end
- Class methods should use the special property ‘static’ for being defined:
function MyClass.static:classMethod()
return 'I am the ' .. self.name .. ' class. I am awesome'
end
- Private methods should be preceded with an underscore:
_myPrivateMethod
File names[Quelltext bearbeiten]
- In general, filenames will be named using semicolons and lowercase letters: my-file-name.lua
- Folders containing a package should be called the same way as the package itself: my_package should be stored under a folder called /my-package/
- If a file only defines a class, it should be named like the class, including lowercase/uppercase: a class named MyClass should be defined on a file named MyClass.lua
- Same happens with Mixins: the file defining MyMixin should be called MyMixin.lua
- If a class is so big it needs to be split on several files, precede all the files defining this class with the class name, followed by an underscore and an explanation of what the file defines:
Game.lua Game_MainMenu.lua Game_OptionsMenu.lua Game_Play.lua
Private members[Quelltext bearbeiten]
There are several ways you can make private parameters with middleclass.
Underscoring[Quelltext bearbeiten]
The simplest one is just to precede your attributes with underscores. This is actually written on the Lua 5.1 reference, section 2.1, “Lexical conversions”, as a way to say “this is here, but please don’t use it”.
local class = require('middleclass')
MyClass = class('MyClass')
function MyClass:initialize()
self._internalStuff = 1
self.publicStuff = 2
end
However, this isn’t really making the properties “hidden”.
Private class attributes[Quelltext bearbeiten]
In general, the way of “really” getting hidden functions or variables consists on using Lua’s scoping rules. The simplest way of using this is creating each of your classes on separate files, and then declaring any private variable or functions as local, on the “root” scope of the file.
Example:
-- File 'MyClass2.lua'
local class = require('middleclass')
MyClass2 = class('MyClass2')
local _internalClassCounter = 0
function MyClass2:initialize()
_internalClassCounter = _internalClassCounter + 1
self.publicStuff = 2
end
function MyClass2:getCount()
return(_internalClassCounter)
end
The scope of local declarations on a lua file is the file itself. If you declare something “local” in one file it is not available on others, even if they “require” that file.
-- File 'main.lua'
require('MyClass2')
-- Try to change internal member...
_internalClassCounter = 4 -- Attempt to modify the _internalClassCounter variable
mw.log(MyClass2:getCount()) -- prints "0"
Let me explain what happens here. The _internalClassCounter = 4 line is, in reality, creating a new global variable called internalClassCounter, and assigning it 4. The “really internal” one is “out of reach” on main.lua (unless someone does really tricky stuff with the environments). So getCount() works as expected.
Private instance methods[Quelltext bearbeiten]
It is also possible to declare private methods. The trick here is not to “include” them on the class definition. On the following example, we will not declare it on Class3:secretMethod
; instead we’ll create a local function. Since we’re not using the : operator any more, we have to make the “self” parameter explicit. Also, since we have to make it local, we have to deviate from the “usual” way of declaring Lua functions (the “usual” way of declaring functions makes them global):
-- File 'MyClass3.lua'
local class = require('middleclass')
MyClass3 = class('MyClass3')
local _secretMethod = function(self) -- notice the 'local' at the beginning, the = function and explicit self parameter
return 'My name is ' .. self.name .. ' and I have a secret.'
end
function MyClass3:initialize(name)
self.name = name
end
function MyClass3:shout()
return _secretMethod(self) .. ' You will never know it!'
end
-- File 'Main.lua'
require('MyClass3')
peter = MyClass3:new('peter')
mw.log(peter:shout()) -- My name is peter and I have a secret. You will never know it!
mw.log(_secretMethod(peter)) -- throws an error - _secretMethod is nil here.
This technique also allows the creation of private class methods. In MiddleClass, there’s really no difference between class methods and instance methods; the difference comes from what you pass to their ‘self’ parameter. So if you invoke _secretMethod like this: _secretMethod(MyClass3) it will be a class method.
A slightly more efficient way of creating a class method would be getting rid of the ‘self’ parameter and use MyClass3 directly on the method’s body:
MyClass3 = class('MyClass3')
local _secretClassMethod = function() -- self parameter out
return 'My name is ' .. MyClass3.name .. ' and I have a secret.' -- use MyClass3 directly.
end
-- Note that this alternative is only recommended for private class methods. Public class methods should follow the convention of adding one explicit 'class' parameter:
MyClass3 = class('MyClass3')
function MyClass3.classMethod(theClass)
return( 'Being a public class named ' .. theClass.name .. ' is not a bad thing.' )
end
This gives a bit more of flexibility when overriding public class methods on subclasses.
Finally, a subtle point regarding recursive private methods. If you need to create a private method that calls himself, you will need to declare the variable first, and then (on the next line) initialize it with the function value. Otherwise the variable will not be available when the function is created
MyClass3 = class('MyClass3')
local _secretRecursiveMethod -- variable declared here
_secretRecursiveMethod= function(self, n) -- and initialized here
if(n<=0) then
return 'Last recursion'
else
print ( 'Recursion level ' .. n )
_secretRecursiveMethod(self, n-1)
end
end
function MyClass3:recurseOver(n)
_secretRecursiveMethod(self, n)
end
m = MyClass3:new()
m:recurseOver(5)
Output:
Recursion level 5 Recursion level 4 Recursion level 3 Recursion level 2 Recursion level 1 Last recursion
Private Instance attributes[Quelltext bearbeiten]
Instance attributes are a little bit trickier to implement, since we only have one scope to “hide stuff in”, and it has to cope with multiple instances.
One way to do this is using one private class variable as a ‘stash’. If you use one table instead of just a number, you can and hide there all the private information you may need. One problem with this approach is that you need to come out with a ‘key’ per ‘instance’.
Fortunately this is a very simple thing to do, since in lua you can use nearly any type of object as a key – So you can use the instances themselves as keys. In other words, we use ‘self’ as a key.
One problem with this approach is that instances might not be liberated by the garbage collector once they are not used any more (since there’s a reference to them on the ‘stash’ keys). In order to avoid this, we can make the ‘stash’ a weak table.
On the following example, the name attribute is public, but age and gender are private.
Our ‘secret stash’ in the following example will be called _private.
-By the way, the following example also shows how you can do “read-only-ish attributes”: you make them private, and make getters for them, but not setters.
-- File 'MyClass4.lua'
local class = require('middleclass')
MyClass4 = class('MyClass4')
local _private = setmetatable({}, {__mode = "k"}) -- weak table storing all private attributes
function MyClass4:initialize(name, age, gender)
self.name = name
_private[self] = {
age = age,
gender = gender
}
end
function MyClass4:getName() -- shorter equivalent: MyClass4:getter('name')
return self.name
end
function MyClass4:getAge()
return _private[self].age
end
function MyClass4:getGender()
return _private[self].gender
end
-- File 'main.lua'
require('MyClass4')
stewie = MyClass4:new('stewie', 2, 'male')
mw.log(stewie:getName()) -- stewie
stewie.name = 'ann'
mw.log(stewie.name) -- ann
mw.log(stewie:getAge()) -- 2
stewie.age = 14 -- this isn't really modifying the age... it is creating a new public attribute called 'age'
-- the internal age is still unaltered
mw.log(stewie:getAge()) -- 2
-- the newly created external age is also available.
mw.log(stewie.age) -- 14
-- same goes with gender:
mw.log(stewie:getGender()) -- 'male'
stewie.gender = 'female'
mw.log(stewie:getGender()) -- 'male'
mw.log(stewie.gender) -- 'female'
Private members on the same file[Quelltext bearbeiten]
There’s also a way of creating private members that other classes/methods on the same file can’t access, if you ever had the need.
Just create an artificial scope with do … end, and declare private members as ‘local’ inside that block. Only the methods inside that block will have access to them:
-- File 'MyClass3.lua'
local class = require('middleclass')
MyClass3 = class('MyClass3')
function MyClass3:initialize(name)
self.name = name
end
do
local secretMethod = function(self) -- notice the explicit self parameter here.
return 'My name is ' .. self.name .. ' and I have a secret.'
end
function MyClass3:shout()
return secretMethod(self) .. ' You will never know it!'
end
end
-- functions outside the do-end will not 'see' secretMethod, but they will see MyClass3.shout (because they see MyClass3)
MIT License[Quelltext bearbeiten]
Copyright (c) 2011 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.