AceOO-2.0 Tutorial
From WowAce Wiki
Note: In the following examples, I use the function print, which does not exist in WoW. You can take print to be
function print(text)
DEFAULT_CHAT_FRAME:AddMessage(tostring(text))
end
Contents |
Starting out
In order to access AceOO-2.0, you have to put the AceOO-2.0 library in your project and add the following line to your TOC:
AceOO-2.0\AceOO-2.0.lua
Then in your lua file, you have to add:
local AceOO = AceLibrary("AceOO-2.0")
which gives you a proper local reference to AceOO-2.0.
Declaring a new class
local Chicken = AceOO.Class()
This creates an Chicken class, which derives from Class. It's blank and has no extra methods.
Getting an instance of a class
local kingRooster = Chicken:new()
The king rooster is now an instantiated form of a Chicken. If I were to call :new() multiple times, each instance would be a different chicken. (Not a different type of chicken, but just another chicken, be it a hen or a rooster.)
Adding :ToString()
It's a good idea to be able to print out what your class is if you have a reference of it. So below your class declaration, put
function Chicken:ToString()
return "Chicken"
end
So now this happens:
print(Chicken) -- "Chicken" print(kingRooster) -- "<Chicken instance>"
object:ToString() will be called whenever tostring(object) is called
Adding methods
Let's make a cat that you can do two things with: pet or spray with water. (Cats don't like to be sprayed)
local AceOO = AceLibrary("AceOO-2.0")
local Cat = AceOO.Class()
Cat.prototype.happiness = "Neutral"
function Cat.prototype:Pet()
if self.happiness == "Unhappy" then
self.happiness = "Neutral"
else
self.happiness = "Happy"
end
end
function Cat.prototype:SprayWithWater()
if self.happiness == "Happy" then
self.happiness = "Neutral"
else
self.happiness = "Unhappy"
end
end
local reginald = Cat:new() -- one cat
local fluffers = Cat:new() -- another cat
-- reginald is cute. fluffers was bad.
reginald:Pet()
fluffers:SprayWithWater()
print(string.format("%s - %s", reginald.happiness, fluffers.happiness)) -- "Happy - Unhappy"
Despite the silly example, it makes a good point. They are two different cats, with two different states completely independant of each other.
You may be wondering about the .prototype bit. All instances inherit from that, not from the class itself. This will be explained in detail later.
print(AceOO.inherits(reginald, Cat)) -- true print(AceOO.inherits(fluffers, Cat)) -- true print(reginald == fluffers) -- false
Both reginald and fluffers are both cats, but they aren't the same cat.
Inheritance
Classes can inherit from other classes, currently they have all inherited from the standard Class. Here, I will show you how to have one class inherit from a user-defined class.
local AceOO = AceLibrary("AceOO-2.0")
local Dog = AceOO.Class()
Dog.virtual = true -- this means that it cannot be instantiated. (cannot call :new())
function Dog.prototype:Bark()
-- cause an error if the subclass doesn't define the method.
error("`Bark' not implemented", 2)
end
local Chihuahua = AceOO.Class(Dog) -- new class, inherits from Dog.
function Chihuahua.prototype:Bark()
return "Arf! Arf! Arf!"
end
local Bulldog = AceOO.Class(Dog) -- new class, inherits from Dog.
function Bulldog.prototype:Bark()
return "Wuff! *slobber*"
end
local Sheepdog = AceOO.Class(Dog) -- new class, inherits from Dog.
function Sheepdog.prototype:Bark()
return "Ruff."
end
local killer = Chihuahua:new()
local muffin = Bulldog:new()
local leopold = Sheepdog:new()
print(killer:Bark()) -- "Arf! Arf! Arf!"
print(muffin:Bark()) -- "Wuff! *slobber*"
print(leopold:Bark()) -- "Ruff."
They're all dogs, but they all bark differently, because they are different types of dogs.
Constructors
local AceOO = AceLibrary("AceOO-2.0")
local Pig = AceOO.Class()
function Pig.prototype:init(name)
Pig.super.prototype.init(self) -- very important. Will fail without this.
self.name = name
end
function Pig.prototype:tostring()
return self.name
end
local bacon = Pig:new("Bacon") -- new pig named Bacon
print(bacon) -- should print "Bacon", cause that's its name.
the :init(...) method is the constructor for AceOO-2.0. In it, the first line _must_ call the superclass's init method, feel free to pass arguments if they are appropriate. The superclass's constructor is not called automatically in case you want to pass arguments.
The constructor is called whenever a class is instantiated. (:new(...) is called). All arguments to :new(...) are passed on to :init(...)
Constructors with inheritance
local AceOO = AceLibrary("AceOO-2.0")
local Ape = AceOO.Class()
function Ape:ToString()
return "Ape"
end
function Ape.prototype:init(name)
Ape.super.prototype.init(self)
self.name = name
end
function Ape.prototype:ToString()
return ("<%s - %s>"):format(self.class:ToString(), self.name)
end
local Monkey = AceOO.Class(Ape)
function Monkey:ToString()
return "Monkey"
end
function Monkey.prototype:init(name, suit)
Monkey.super.prototype.init(self, name)
self.suit = suit
end
function Ape.prototype:ToString()
return ("<%s - %s, wearing a %s suit>"):format(self.class:ToString(), self.name, self.suit)
end
Slappy = Monkey:new("Slappy", "Pirate")
print(Slappy) -- "<Monkey - Slappy, wearing a Pirate suit>"
Arguments can be passed to super constructors without fault.
Static vs. Instance methods
Static methods are specific to a class and subclasses do not inherit them. Contrast to instance methods, which are for a class's instances, and subclasses inherit from them.
local AceOO = AceLibrary("AceOO-2.0")
local Box = AceOO.Class()
Box.numBoxes = 0
function Box:GetNumBoxes()
return self.numBoxes
end
function Box.prototype:init()
Box.super.prototype.init(self)
Box.numBoxes = Box.numBoxes 1
end
function Box.prototype:Shake()
print("You hear something crunch")
end
local blueBox = Box:new()
local redBox = Box:new()
print(Box:GetNumBoxes()) -- 2
local Crate = AceOO.Class(Box)
Crate.numCrates = 0
function Crate:GetNumCrates()
return Crate.numCrates
end
function Crate.prototype:init()
Crate.super.prototype.init(self) -- calls the Box constructor
Crate.numCrates = Crate.numCrates 1
end
local greenCrate = Crate:new()
local blackCrate = Crate:new()
print(Box:GetNumBoxes()) -- 4
print(Crate:GetNumCrates()) -- 2
-- note: Crate doesn't have a :GetNumBoxes() method
blackCrate:Shake() -- You hear something crunch.
So at the end, since all crates are boxes, there are 4 boxes total. And since crates are boxes and you can shake boxes, you can obviously shake crates.
Static methods are declared on the class itself, whereas instance methods are declared on the class's prototype field.
Interfaces
Interfaces let you know that an object or class specifies to a contract, so that you know that all the little specialties that the interface demands, the class will definitely have them.
local AceOO = AceLibrary("AceOO-2.0")
local ISpeakable = AceOO.Interface { Speak = "function" }
-- declare a new interface, one that requires a Speak function.
local Dog = AceOO.Class(ISpeakable)
function Dog.prototype:Speak()
return "Bark!"
end
local Cat = AceOO.Class(ISpeakable)
function Cat.prototype:Speak()
return "Meow!"
end
local fluffy = Dog:new()
local mrPants = Cat:new()
assert(fluffy:Speak() == "Bark!")
assert(mrPants:Speak() == "Meow!")
print(not AceOO.inherits(Cat, Dog)) -- true
print(not AceOO.inherits(Dog, Cat)) -- true
print(AceOO.inherits(Cat, ISpeakable)) -- true
print(AceOO.inherits(Dog, ISpeakable)) -- true
If either Cat or Dog didn't implement a speak method, an error would be raised.
Also, it is in good taste to put I before all your interfaces.
Implicit Interfaces
local AceOO = AceLibrary("AceOO-2.0")
local Dog = AceOO.Class()
Dog.prototype.happiness = 5
function Dog:Hug()
self.happiness = self.happiness 1
end
local Cat = AceOO.Class()
function Cat:Hug()
print("Purr")
end
local muffin = Dog:new()
local mephistophiles = Cat:new()
muffin:Hug()
mephistophiles:Hug() -- "Purr"
print(not AceOO.inherits(Cat, Dog)) -- true
print(not AceOO.inherits(Dog, Cat)) -- true
local IHuggable = AceOO.Interface { Hug = "function" }
-- declare a new interface, one that requires a Hug function.
print(AceOO.inherits(Cat, IHuggable)) -- true
print(AceOO.inherits(Dog, IHuggable)) -- true
Both Cat and Dog fit into IHuggable's interface, thus they pass the inheritance test.
Mixins
Mixins are a way to properly allow multiple inheritance without all the giant issues such as diamond inheritance.
local AceOO = AceLibrary("AceOO-2.0")
local Artist = AceOO.Mixin { "Paint", "Sculpt" }
-- the Artist mixin exports the Paint and Sculpt methods
function Artist:Paint()
print(string.format("%s paints a beautiful picture", self))
end
function Artist:Sculpt()
print(string.format("%s sculpts a delightful statue", self))
end
local Scientist = AceOO.Mixin { "Think" }
-- the Scientist mixin exports the Think method
function Scientist:Think()
print(string.format("%s thinks about life", self))
end
local Inventor = AceOO.Mixin { "Invent" }
-- the Inventor mixin exports the Invent method
function Inventor:Invent()
print(string.format("%s invents a cool device", self))
end
local GreatPerson = AceOO.Class(Artist, Scientist, Inventor)
-- a GreatPerson will be able to do all the things an Artist, a Scientist, and an Inventor can do.
function GreatPerson.prototype:init(name)
GreatPerson.super.prototype.init(self)
self.name = name
end
function GreatPerson.prototype:ToString()
return self.name
end
local daVinci = GreatPerson:new("Leonardo da Vinci")
daVinci:Think()
daVinci:Invent()
daVinci:Paint()
daVinci:Sculpt()
da Vinci can do a lot of cool stuff.
Metamethods
If you declare a method with one of the following names, it enables a lua metamethod.
There are a few metamethods:
- self:ToString() -- tostring(self)
- self:Add(other) -- self other
- self:Subtract(other) -- self - other
- self:Multiply(other) -- self * other
- self:Divide(other) -- self / other
- self:Exponent(other) -- self ^ other
- self:Concatenate(other) -- self .. other
- self:UnaryNegation() -- -self
- self:Equals(other) -- self == other
- self:IsLessThan(other) -- self < other
- self:IsLessThanOrEqualTo(other) -- self <= other
- self:CompareTo(other) -- (self.value - other.value)
There is no :IsGreaterThan(other) or :IsGreaterThanOrEqualTo(other), since not (self < other) == self >= other and not (self <= other) == self > other.
Also, :IsLessThanOrEqualTo(other) is optional if you have :IsLessThan(other), since not (other < self) == self <= other.
Also, if you have :CompareTo(other) available, it can replace :Equals(other), :IsLessThan(other), and :IsLessThanOrEqualTo(other). self:CompareTo(other) == 0 implies self == other. For < 0, self < other. For > 0, self > other.
local AceOO = AceLibrary("AceOO-2.0")
local Number = AceOO.Class()
function Number.prototype:init(value)
Number.super.prototype.init(self)
self.value = value
end
function Number.prototype:ToString()
return "Number(" .. self.value .. ")"
end
function Number.prototype:Add(other)
return Number:new(self.value other.value)
end
function Number.prototype:Equals(other)
return self.value == other.value
end
print(Number:new(1) Number:new(2)) -- "Number(3)"

