Skip to content
This repository has been archived by the owner on Sep 19, 2020. It is now read-only.

Classes

Oliver Cooper edited this page Jan 11, 2016 · 1 revision

Contents


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

Defining a Class and Properties

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

Example

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

A note for people continuing from Getting Started

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.

A note regarding accessing properties

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.

Functions in classes

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

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.

Calling an instance function

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.

A warning on defining functions in the properties table

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

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 Persons? For example, we want all Persons 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

Calling a static function

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

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

Getters and Setters

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.

Getters

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.

Warning on calling getter functions directly

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

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.