|
|
(3 intermediate revisions by 2 users not shown) |
Line 1: |
Line 1: |
− | __TOC__
| + | https://github.com/kikito/middleclass |
| | | |
− | This is an object-oriented library for Lua.
| + | A clean object-orientation framework for Lua, well-documented and tested. |
| | | |
− | If you are familiar with Object Orientation in other languages (C++, Java, Ruby ... ) then you will probably find this library very easy to use.
| + | ==Features== |
| + | * Metamethod support(except __index and __newindex) |
| + | * Straightforward and fast |
| + | * [https://github.com/kikito/middleclass-commons Class-Commons support] |
| | | |
− | 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.
| + | ==Docs== |
− | | + | The documentation can be found on the [https://github.com/kikito/middleclass/wiki Github wiki] |
− | MiddleClass was developed as a key part of the [http://love2d.org/forum/viewtopic.php?f=5&t=1039 PÄSSION] lib.
| |
− | | |
− | MiddleClass has an optional module called [[MindState]], which adds stateful capacities to objects.
| |
− | | |
− | ==Questions==
| |
− | Please refer to the [http://love2d.org/forum/viewtopic.php?f=5&t=1053 forum post] if you have any questions/issues/bugfixes.
| |
− | | |
− | ==Main Features==
| |
− | This library provides:
| |
− | * A Root class called Object
| |
− | * Classes can be subclassed(single inheritance)
| |
− | * Instances are created with Class:new, and initialized with Class:initialize
| |
− | * There's a super facility that enables calling methods on the superclass'. This is especially useful on constructors (super.initialize)
| |
− | * The function subclassOf(Class1, Class2) returns true if Class2 is a subclass of Class1, and false otherwise
| |
− | * The function instanceOf(Class1, inst) returns true if inst is an instance of class1, and false otherwise
| |
− | * You can declare class methods and attributes
| |
− | | |
− | ==Example==
| |
− | <source lang="lua">
| |
− | require 'MiddleClass.lua'
| |
− | | |
− | Person = class('Person') --this is the same as class('A', Object) or Object:subclass('A')
| |
− | function Person:initialize(name)
| |
− | self.name = name
| |
− | end
| |
− | function Person:speak()
| |
− | print('Hi, I am ' .. self.name ..'.')
| |
− | end
| |
− | | |
− | AgedPerson = class('AgedPerson', Person) -- or Person:subclass('AgedPerson')
| |
− | AgedPerson.ADULT_AGE = 18 --this is a class variable
| |
− | function AgedPerson:initialize(name, age)
| |
− | super.initialize(self, name) -- this calls the parent's constructor (Person.initialize) on self
| |
− | self.age = age
| |
− | end
| |
− | function AgedPerson:speak()
| |
− | super.speak(self) -- prints "Hi, I am xx."
| |
− | if(self.age < AgedPerson.ADULT_AGE) then --accessing a class variable from an instance method
| |
− | print('I am underaged.')
| |
− | else
| |
− | print('I 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)
| |
− | p1:speak()
| |
− | p2:speak()
| |
− | | |
− | --[[ output:
| |
− | Hi, I'm Billy the Kid.
| |
− | I am underaged.
| |
− | Hi, I'm Luke Skywalker.
| |
− | I am an adult.
| |
− | ]]
| |
− | </source>
| |
− | | |
− | ==Source Code==
| |
− | The latest version can be obtained from its [http://github.com/kikito/middleclass github page].
| |
− | * Here's a link to the latest [http://github.com/kikito/middleclass/raw/master/MiddleClass.lua raw file]
| |
− | * And here you have a [http://github.com/kikito/middleclass/blob/master/MiddleClass.lua syntax highlighted version]
| |
− | | |
− | If you are interested in actually understanding the code, you might want to start with this short guide. Once you understand this basic structure, you can start "digging"
| |
− | <source lang="lua">
| |
− | -- difficulty
| |
− | <...> -- 2
| |
− | | |
− | function Object.new() <...> end -- 7
| |
− | function Object.subclass() <...> end -- 8 most complicated
| |
− | function Object.includes() <...> end -- 5
| |
− | | |
− | <...> -- 6
| |
− | | |
− | function subclassOf(superClass, subClass) <...> end -- 3
| |
− | function instanceOf(class, instance)<...> end -- 4
| |
− | function class(name, baseClass) <...> end -- 1 easiest
| |
− | </source>
| |
− | | |
− | I have numbered the pieces in ascending difficulty - class is the easiest to understand and Object.subclass the most difficult one. Good luck!
| |
− | | |
− | =Naming conventions=
| |
− | | |
− | 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 MindState or PÄSSION)
| |
− | | |
− | ==Classes and packages==
| |
− | * Package names should be lowercased camelCased: package
| |
− | * 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:
| |
− | <source lang="lua">
| |
− | MyClass = class('package.MyClass')
| |
− | </source>
| |
− | * Another exception is for internal classes (classed declared inside classes)
| |
− | <source lang="lua">
| |
− | MyClass = class('package.MyClass')
| |
− | MyClass.InternalClass = class('package.MyClass.InternalClass')
| |
− | </source>
| |
− | | |
− | ==Attributes, instances and constants==
| |
− | * 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==
| |
− | * 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:
| |
− | <source lang="lua">
| |
− | function MyClass:setX(x)
| |
− | self.x = x
| |
− | end
| |
− | </source>
| |
− | * Class methods should use dots, and an explicit 'theClass' parameter, so they are easily identifiable
| |
− | <source lang="lua">
| |
− | function MyClass.classMethod(theClass)
| |
− | print('I am the ' .. theClass.name .. ' class. I am awesome')
| |
− | end
| |
− | </source>
| |
− | * Private methods should be preceded with an underscore: _myPrivateMethod
| |
− | | |
− | ==File names==
| |
− | * Folders containing a package should be called the same way as the package itself: myPackage should be stored under a folder called also myPackage
| |
− | * If a file defines a class named MyClass, then the file should be called MyClass.lua
| |
− | * Files defining mixins should also be called after the mixins they define: MyMixin will be defined in 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:
| |
− | <source lang="lua">
| |
− | Game.lua
| |
− | Game_MainMenu.lua
| |
− | Game_OptionsMenu.lua
| |
− | Game_Play.lua
| |
− | </source>
| |
− | | |
− | =Advanced Features=
| |
− | * There's a very rudimentary support for [http://en.wikipedia.org/wiki/Mixin mixins]
| |
− | * You can specify Lua's metamethods as methods on the classes, and they will be used by the instances. In other words, if you define the method C.__tostring, then all instances of class C will be "stringizables", the Lua way.
| |
− | * MiddleClass has now an standarized way to create getter and setter methods (a.k.a. [http://en.wikipedia.org/wiki/Mutator_method mutator methods]).
| |
− | | |
− | ==Mixins==
| |
− | Mixins can be used for sharing methods between classes, without requiring them to inherit from the same father.
| |
− | <source lang="lua">
| |
− | HasWings = { -- HasWings is a module, not a class. It can be "included" into classes
| |
− | fly = function ()
| |
− | print('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()
| |
− | bee:fly()
| |
− | bat:fly()
| |
− | | |
− | --[[ output:
| |
− | flap flap flap I am a Bee
| |
− | flap flap flap I am a Bat
| |
− | ]]
| |
− | </source>
| |
− | | |
− | 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. Any additional parameters passed to class:include will be passed to mixin:included()
| |
− | <source lang="lua">
| |
− | DrinksCoffee = {
| |
− | included = function(class, coffeeTime) {
| |
− | print(class.name ' drinks coffee at ' .. coffeeTime)
| |
− | class.coffeeTime = coffeeTime
| |
− | }
| |
− | }
| |
− | | |
− | -- 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
| |
− | print(self.name .. ': It is not the time to drink coffee!')
| |
− | else
| |
− | print(self.name .. ': Mmm I love coffee at ' .. drinkTime)
| |
− | end
| |
− | end
| |
− | | |
− | EnglishMan = class('EnglishMan')
| |
− | EnglishMan:include(DrinksCoffee, 5)
| |
− | function EnglishMan:initialize(name) self.name = name end
| |
− | | |
− | Spaniard = class('Spaniard')
| |
− | Spaniard:include(DrinksCoffee, 6)
| |
− | function Spaniard:initialize(name) self.name = name end
| |
− | | |
− | tom = EnglishMan:new('tom')
| |
− | juan = Spaniard:new('juan')
| |
− | | |
− | tom:drink(5)
| |
− | juan:drink(5)
| |
− | 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
| |
− | ]]
| |
− | </source>
| |
− | | |
− | ==Metamethods==
| |
− | [http://lua-users.org/wiki/MetamethodsTutorial Metamethods] can do funky stuff like allowing additions in our instances. Let's make an example with __tostring
| |
− | <source lang="lua">
| |
− | 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)
| |
− | print(p1)
| |
− | print(p2)
| |
− | --[[ output:
| |
− | Point: [100, 200]
| |
− | Point: [35, -10]
| |
− | ]]
| |
− | </source>
| |
− | | |
− | =Private=
| |
− | | |
− | There are several ways you can make private parameters with MiddleClass.
| |
− | | |
− | | |
− | ==Underscoring==
| |
− | | |
− | The simplest one is just to precede your attributes with underscores. This is actually written on the Lua 5.1 reference, section 2.1, "[http://www.lua.org/manual/5.1/manual.html#2.1 Lexical conversions]", as a way to say "this is here, but please don't use it".
| |
− | | |
− | <source lang="lua">
| |
− | require('MiddleClass.lua')
| |
− | MyClass = class('MyClass')
| |
− | | |
− | function MyClass:initialize()
| |
− | self._internalStuff = 1
| |
− | self.publicStuff = 2
| |
− | end
| |
− | </source>
| |
− | | |
− | However, this isn't really making the properties "hidden".
| |
− | | |
− | ==Private class attributes==
| |
− | | |
− | 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:
| |
− | | |
− | <source lang="lua">
| |
− | -- File 'MyClass2.lua'
| |
− | require('MiddleClass.lua')
| |
− | | |
− | MyClass2 = class('MyClass2')
| |
− | | |
− | local _internalClassCounter = 0
| |
− | | |
− | function MyClass2:initialize()
| |
− | _internalClassCounter = _internalClassCounter + 1
| |
− | self.publicStuff = 2
| |
− | end
| |
− | | |
− | function MyClass2:getCount()
| |
− | return(_internalClassCounter)
| |
− | end
| |
− | </source>
| |
− | | |
− | 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.
| |
− | <source lang="lua">
| |
− | -- File 'main.lua'
| |
− | | |
− | require('MyClass2.lua')
| |
− | | |
− | -- Try to change internal member...
| |
− | _internalClassCounter = 4 -- Attempt to modify the _internalClassCounter variable
| |
− | | |
− | print(MyClass2:getCount()) -- prints "0"
| |
− | </source>
| |
− | | |
− | 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 Methods==
| |
− | | |
− | 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):
| |
− | | |
− | <source lang="lua">
| |
− | -- File 'MyClass3.lua'
| |
− | require('MiddleClass.lua')
| |
− | | |
− | 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()
| |
− | print( _secretMethod(self) .. ' You will never know it!' )
| |
− | end
| |
− | | |
− | -- File 'Main.lua'
| |
− | require('MyClass3.lua')
| |
− | | |
− | peter = MyClass3:new('peter')
| |
− | peter:shout() -- My name is peter and I have a secret. You will never know it!
| |
− | | |
− | print(_secretMethod(peter)) -- throws an error - _secretMethod is nil here.
| |
− | | |
− | </source>
| |
− | | |
− | 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:
| |
− | | |
− | <source lang="lua">
| |
− | MyClass3 = class('MyClass3')
| |
− | | |
− | local _secretClassMethod = function() -- self parameter out
| |
− | return( 'My name is ' .. MyClass3.name .. ' and I have a secret.' ) -- use MyClass3 directly.
| |
− | end
| |
− | </source>
| |
− | | |
− | Note that this alternative is only recommended for '''private''' class methods. Public class methods should follow the convention of adding one explicit 'class' parameter:
| |
− | <source lang="lua">
| |
− | MyClass3 = class('MyClass3')
| |
− | | |
− | function MyClass3.classMethod(theClass)
| |
− | return( 'Being a public class named ' .. theClass.name .. ' is not a bad thing.' )
| |
− | end
| |
− | </source>
| |
− | | |
− | 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
| |
− | | |
− | <source lang="lua">
| |
− | MyClass3 = class('MyClass3')
| |
− | | |
− | local _secretRecursiveMethod -- variable declared here
| |
− | _secretRecursiveMethod= function(self, n) -- and initialized here
| |
− | if(n<=0) then
| |
− | print( 'Last recursion')
| |
− | else
| |
− | print ( 'Recursion level ' .. n )
| |
− | _secretRecursiveMethod(self, n-1)
| |
− | end
| |
− | end
| |
− | | |
− | 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
| |
− | ]]
| |
− | </source>
| |
− | | |
− | ==Private Instance attributes==
| |
− | | |
− | 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 [http://lua-users.org/wiki/WeakTablesTutorial 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.
| |
− | | |
− | <source lang="lua">
| |
− | -- File 'MyClass4.lua'
| |
− | require('MiddleClass.lua')
| |
− | | |
− | | |
− | 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.lua')
| |
− | | |
− | stewie = MyClass4:new('stewie', 2, 'male')
| |
− | | |
− | print(stewie:getName()) -- stewie
| |
− | stewie.name = 'ann'
| |
− | print(stewie.name()) -- ann
| |
− | | |
− | print(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
| |
− | print(stewie:getAge()) -- 2
| |
− | -- the newly created external age is also available.
| |
− | print(stewie.age) -- 14
| |
− | | |
− | -- same goes with gender:
| |
− | | |
− | print(stewie:getGender()) -- 'male'
| |
− | stewie.gender = 'female'
| |
− | print(stewie:getGender()) -- 'male'
| |
− | print(stewie.gender) -- 'female'
| |
− | </source>
| |
− | | |
− | ==Private members on the same file==
| |
− | | |
− | 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:
| |
− | | |
− | <source lang="lua">
| |
− | -- File 'MyClass3.lua'
| |
− | require('MiddleClass.lua')
| |
− | | |
− | 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()
| |
− | print( 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)
| |
− | | |
− | </source>
| |
| | | |
| == See Also == | | == See Also == |
| * [[Libraries]] | | * [[Libraries]] |
| | | |
− | {{#set:LOVE Version=0.6.1}} | + | {{#set:LOVE Version=Any}} |
− | {{#set:Description=More object orientation for Lua.}} | + | {{#set:Description=Object-orientation for Lua}} |
| + | {{#set:Keyword=Class}} |
| [[Category:Libraries]] | | [[Category:Libraries]] |