AceOO-2.0 Tutorial

From WowAce Wiki
Jump to: navigation, search

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

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)"