-
Notifications
You must be signed in to change notification settings - Fork 2
Classes
Silica has an extremely powerful and advanced class system, rivalling some fully object oriented languages' systems. The Silica class system is heavily modelled off the Objective-C system, so if you've used that (or any other reasonable object oriented language really) you should feel right at home. In a nutshell, the main features of Silica classes are:
- Inheritance, of course
- Getters and Setters
- The ability to access and call functions on super
- Interfaces (a.k.a protocols and abstract classes)
- Using the class as a function to initialise an instance
- A few small functions that help you do things quicker and easier
Silica classes are created using a special syntax:
class "ClassName" {
property = value;
}
The class name should always be in Pascal case and property values in camel case, see the style guide (TODO) for more information. In addition, the file name for classes should be identical to the class name with .lua on the end and should be in the classes folder.
Properties are values specific to a class' instance, for example, things like a name, colour, etc. Properties should always be defined in the properties table, even if they don't have a value at first. Property values in the table are the default values when a class is initialised
You should also never use nil
as a property value, instead use false
. This is because indexing (getting the value of) nil
values causes the system to assume that you didn't set the value and will look in the super classes (more on those soon).
Lets make a Person
class. Each Person
will have a first name, last name and age. We don't want a Person
instance to have a first or last name when it starts, because not all people have the same name when they are created, so well'll use false
as the starting value (not nil!). However, all people are 0 years old when they are born, so we can use that as a default property.
We can define our class as such:
class "Person" {
age = 0;
firstName = false;
lastName = false;
}
We can then initialise (create an instance of) a Person
like so:
local person = Person()
If you're following on from the Getting Started page you might not have anywhere you can actually run this code yet, so for now just add this code in your ExampleApplication (or whatever you named it) class after the table. It will run every time you start your program. Don't worry about what it means yet.
function ExampleApplication:init() -- Any code you want to play around with can go in here for now local person = Person() end
At this stage if we look at the values of our Person
they will give us the following:
print( person.age ) -- 0
print( person.firstName ) -- false
print( person.lastName ) -- false
Now, as you'd probably guess, we can do the following to set the values of a Person
:
person.firstName = "Oliver"
person.lastName = "Cooper"
print( person.firstName ) -- Oliver
print( person.lastName ) -- Cooper
However, that's a bit of a clumsy way to set a Person
's name every time we make one, especially if we have to do it a lot. We'll solve that in the next section after next.
Where possible you should try and index instances (get their properties) as little as possible. While the same applies to normal Lua tables the effect is significantly greater in Silica. Due to the complexity of the class system there is a lot of code called each index.
So, instead of this:
-- Don't do this print( "Hello there, " .. person.firstName ) print( person.firstName .. ", you are " .. person.age .. " years old." )Do this:
-- Do this local firstName = person.firstName print( "Hello there, " .. firstName ) print( firstName .. ", you are " .. person.age .. " years old." )Use the same name for the local variable as the property if you can. Setting the local variable won't set the property naturally though, careful not to make that mistake.
The first thing you should be aware of with Silica classes - as with most object oriented class' functions - is there are two forms, instance and static functions.
Instance functions are functions you call on an instance (for example, our person called 'Oliver' in the previous example). They do and/or get specific things to or from the instance. For example, making the person walk forward. You're not making all people move forward, just Oliver.
In Silica functions are defined like so after the class definition and property table (not before, that'll crash because you haven't defined the class yet).
function Person:walkForward( distance )
-- your function code here
print( self.firstName ) -- Oliver
end
Simply use the class name and function name separated by a colon (:
). You don't need to include self
in the function arguments, Lua does that for you.
To call an instance function you simply call it like you probably have with other Lua object oriented system.
person:walkForward( 10 )
Note that you always call and define instance functions with a colon, not a full stop.
Never, ever, ever define functions in the properties table! For example, this is bad. Really bad. oeed will, once again, need to inflict serious harm upon you; this time sans-humorous YouTube video.
class "Person" { walkForward = function( self, distance ) -- don't EVER even consider doing this. ever. end }
Static functions work in the opposite manner to instance functions. Rather than calling them on instances you call them on the class itself. What if you wanted to change all Person
s? For example, we want all Person
s to evolve to have three arms.
Static functions are defined in the same way as instance functions except rather than using a colon you use a full stop. In addition, you don't use self
, instead you use the class.
function Person.evolve()
Person.armCount = 3
end
As you might've been able to presume, you call static functions with a full stop, not a colon. You also call it on the class, not and instance (make sure you don't do that).
Person.evolve()
Custom initialisers allow you to pass arguments to the function you call to create an instance and do things with the values. In Objective-C this is the init
function and PHP the __construct
function; it's a common feature for object oriented languages.
Initialisers are defined as follows:
function Person:initialise( firstName, lastName )
self.firstName = firstName
self.lastName = lastName
end
Simply use initialise
as the function name and do what you would for a normal instance function.
In the Person
example, the initialiser now allows the following. Notice how we don't need any extra code as we did previously.
local person = Person( "Oliver", "Cooper" )
print( person.age ) -- 0
print( person.firstName ) -- Oliver
print( person.lastName ) -- Cooper
If you've used Objective-C this concept should be very familiar to you. If you haven't they're not very difficult and make your code much neater.
Getter functions are called when you want to 'get' the value of a property. If you look at the ComputerCraft APIs you'll see functions like os.getComputerID
. Silica uses that same naming style for its getter functions, except rather than calling the function you simply get the value as you normally would.
You create a getter function as below. The first letter after get is always capitalised. In this example, the getter for firstName
is getFirstName
. They are always instance functions.
function Person:getFirstName()
local firstName = self.firstName -- continuing from the previous example, this will be "Oliver"
return string.lower( firstName )
end
If you index the property that the getter function is for (so the function Person:getFirstName
is for self.firstName
) it will be the value that is currently stored in the class.
You can then manipulated the value, as was done in that example, making the string lowercase.
Getter functions are called when you index your instance. This code will call the function we just defined (getFirstName
)
-- this actually calls and returns the value of person:getFirstName.
person.firstName -- oliver
You don't need to return the original value in any form if you don't want. For example, the following is completely valid.
function Person:getAge()
return os.clock() - self.birthTime
end
Getter functions can also return multiple values as normal Lua functions are able to.
You should never call a getter function directly. When indexing a property that has a getter it locks the system from calling the getter again when you index the property within the function.
Directly calling a getter function will result the getter in being called twice. Use the property notation, that's what the system is there for.
So, don't ever do this:
local firstName = person:getFirstName() -- NOOOOO!!! BAD BAD BAD!Do this instead:
local firstName = person.firstName -- Good!
Setters do the opposite to getters. Rather than return the value they set value.
As you'd predict, setters are defined like so, again capitalising the first letter after 'set'.
function Person:setLastName( lastName )
self.lastName = string.upper( lastName )
end
The setter will then be called when you set the property, like below.
person.lastName = "Cooper"
print( person.lastName ) -- COOPER
As with getters, you should never call the setter directly. Read the warning in the getter section for more details, the same principle applies.
This is bad:
person:setLastName( "Cooper" ) -- Don't do this!!!!
This is good:
person.lastName = "Cooper" -- Do this!
As a result, you cannot have a setter with more than one argument. If you need to do that (and you probably don't) then make another function that sets the property.