OOP for LiveCode - Part One
Have you ever wished that LiveCode would be more object oriented? That you could create "real" objects? Writing real classes instead of just using behaviors. If you completely understand what I'm talking about you can skip the next Object Orientation Programming (OOP) primer and jump directly to the juicy stuff down below. If you're thinking, "Hey, Whats OOP? Is that Scottish for "Hey, What's up?"" then read on.
There are four cornerstones of object orientation:
So lets start coding! We are going to create a new LiveCode library, so start by creating a new stack and name it "OOPengine". To create a new object we add a new function to the stack that will return our new "object"
function newObject pClass pName # Create a group and set some basic properties create group pName set the margins of the last group to 0 # Make it zero pixel \ size return the long id of the last group end newObject
We also need to be able to delete our objects, so let's add a delete procedure:
on deleteObject pObject # Check if object exists if exists(pObject) then delete pObject else # Maybe we got just an object name? if exists(group pObject) then deleteObject(the long id of group pObject) else throw "Object doesn't exists" end if end if end deleteObject
The implementation of deleteObject makes it possible to call the method both with the long id of the object and also the name. But still, that doesn't give much, we can't set the class of the object. Lets expand the code a bit to include the class. As we use behaviors as the "class" we will need to use a button as the class definition. In this implementation I have for simplicity placed all "classes" i.e. buttons on a single card called "Classes". Then we can change the code of our newObject to:
function newObject pClass pName #check if the "class" exists on card "Classes" if exists(button pClass of card "Classes") then # Create a group and set some basic properties create group pName set the margins of the last group to 0 # Make it zero \ pixel size # Now we can set it's "class" set the behavior of the last group to the long id of \ button pClass of card "Classes" return the long id of the last group else # There is no class named pClass so we throw an error as # this # is a programming error throw "Class definition doesn't exists" end if end newObject
Ok, so now we can create objects that have a class but how do we implement inheritance? Well in LiveCode if you put a group in another group the inner group "inherits" from the outer group (Aha! I told it was important didn't I!) Messages to a group travel upwards in the hierarchy, even properties implemented with setProp and getProp are inherited! To know which class is the parent class we add a property "parent" to our class if it inherits from another class. Then we need to encapsulate our "class"-group in it's parent class. We need to expand our newObject again to it's final version.
function newObject pClass pName local parentObject, newObject #check if the "class" exists on card "Classes" if exists(button pClass of card "Classes") then # Do we have a parent to inherit from? if the parent of button pClass of card "Classes" is not \ empty then put newObject(the parent of button pClass of card \ "classes") into parentObject end if # Now create the group in it's parent if it has any if parentObject is not empty then create group pName in parentObject else create group pName end if set the margins of the last group to 0 # Make it invisible set the behavior of the last group to the long id of \ button pClass of card "Classes" return the long id of the last group else # There is no class named pClass so we throw an error as # this # is a programming error throw "Class definition doesn't exist" end if end newObject
The idea is to check if the class has a parent then first recursively create the parent and then create the subclass. As we now can have groups within groups within groups... we also need to change the deleteObject:
on deleteObject pObject local ownerObject # Check if object exists if exists(pObject) then # store the owner so we can delete it later put the owner of pObject into ownerObject delete pObject # Check if it is a group so we don't accidently delete # the card if ownerObject begins with "group" then deleteObject ownerObject end if else # Maybe we got just an object name? if exists(group pObject) then deleteObject(the long id of group pObject) else throw "Object doesn't exists" end if end if end deleteObject
Now we have a somewhat complete definition so lets test it out. Create a new stack and add the following preOpenStack:
on preOpenStack start using stack "OOPEngine" end preOpenStack
Create a new card and name it "Classes". Create a button on the "Classes" card, name it "Vehicle" and add the following code to the button:
local _weight setProp weight pWeight if pWeight is a number and pWeight > 0 then put pWeight into _weight end if end weight getProp weight return _weight end weight
Next we create a new class button "Car" and add the following code:
getProp parent return "Vehicle" end parent local _engineEffect setProp horsePower pEffect if pEffect is a number and pEffect > 0 then put pEffect into _engineEffect end if end horsePower getProp horsePower return _engineEffect end horsePower
I have placed the "parent" property at the top even before the variable definitions. This is not needed but is good practice for clarity. Now we can test it out, so go back to the first card and add a button with the following code:
on mouseUp put newObject("Car", "Lamborgini") into myCar set the weight of myCar to 1507 set the horsePower of myCar to 523 answer "Car weight:" & the weight of myCar & \ "kg. Car engine horsepower: " & the horsePower of myCar deleteObject myCar end mouseUp
Apply and press the button and you should see a dialog displaying the data. One coming from the base class (Vehicle) and one coming from the subclass (Car).
Calling Methods in an Object
function login pUserName, pPassword # Check login data and return true if successful return true end login
To call that function within our player object we can use either value or dispatch:
# Create a new object of class "Player" put newObject("Player") into _player put value("login(playerName,passwd)", _player) into loginSuccess dispatch function "login" to _player with playerName, passwd put the result into loginSuccess
Value has the advantage of being a one-liner but the work to create the first parameter is usually not worth the effort as the line will be hard to read. If playerName and passwd above are variables the line above would need to be:
put value("login(" & quote & playerName & quote & comma & quote \ & passwd & quote & ")", _player) into loginSuccess
I wrapped all this up in a pre built library stack which you can download here. It also contains some convenience functions but more on that in the second part!
You can also download the Particles.livecode example stack to get a small example of the engine working.
NOTE: Some might object that your stack will grow fast when using a lot of groups within groups. But according to my tests LiveCode is very memory efficient and doesn't occupy a lot of memory for things that might be set in a control. An empty group is just over 100 bytes, and for that you get persistent objects for free!