revUp - Updates and news for the LiveCode community
Issue 143 | November 9th 2012 Contact the Editor | How to Contribute

OOP for LiveCode - Part One
Have you ever wished that LiveCode was more object oriented? Read on...

by Hakan Liljegren

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.

OOP Primer

In an object oriented program you can see your program as a collection of objects that responds to messages. Just as the controls in LiveCode can respond to messages, send messages to other controls, hold their own values etc. But in LiveCode you are limited to the "objects" i.e. the controls that LiveCode provides. You can't create an object "Car" which will hold engine, wheels, price, top speed, etc.

There are four cornerstones of object orientation:

Encapsulation
In the object every property should be encapsulated from its internal implementation. If you have a property "topSpeed" you should write a setter and a getter function. If you then need to change your internal representation your other objects can be left untouched as nothing has changed in their opinion.

Abstraction
The abstraction of an object is closely connected to encapsulation. In an object you don't need to know the inner workings (implementation) of your object. You only need to know the methods and properties, so you can happily live with your abstract view of your object. The abstraction is the defined protocol for the object and is called its class.

Inheritance
The idea with inheritance is that some objects inherit some of their properties from a common ancestor. Both a car and a bicycle is a kind of vehicle and have some properties in common (like every vehicle has a weight) and some are unique to that kind of vehicle (like a car has an engine, but bicycle has not).

It is common practice that every object has a common ancestor often called the root class from which every object inherits.

Polymorphism
Polymorphism is that you can have several different objects that have the same protocol (i.e. commands and functions) but have different implementations. We have that in LiveCode controls today. Every control has a width and height property. When you change the width of an image and a graphic the inner workings are quite different but, as that complexity is abstracted from you, you never have to care.

Benefits of OOP

There are several benefits to OOP. As every object can only be reached via its protocol you will isolate your code chunks and break it up into smaller discrete pieces. That makes it easier to fix bugs, as all functionality of a specific object is centralized into one code. Another benefit for LiveCoders is that you will not bloat your stack script with several thousands of lines of code as you may now separate them into smaller objects that have their code elsewhere. Yet another benefit is that it will be easier to reuse your classes among projects.

LiveCode Objects

There isn't an "object" or "class" in LiveCode but we can make one by using a few lines of code and using groups as our class instances and behaviors as the class protocol. The benefits of using groups is that an empty group with no contents is invisible and that a group can contain groups, but why this is important we save for later.

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
As you can see above you can use getProp and setProp to communicate with your object, but that is rather limited as you can't send data to a getProp and only one parameter is allowed to setProp. What if I want a "player" object with a "login" function which takes a username and a password and I want to get back a value of true or false if the login went OK:

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!

Happy Coding!

Hakan

About the Author

Hakan Liljegren lives in Karlstad, Sweden. He soldered his own computer in 1981, and has not stopped programming since. When not writing LiveCode apps for his company Exformedia he loves to ride his bike. Even in the winter!

 

 

 

Main Menu

What's New


Buy Beginners LiveCode Book