Roll Your Own
The Object of Programming
We’ve come a long way since the beginning of this series some months ago. We’ve gone from the very basics of what it means to write a program, to being able to create our own software with nothing more than the computer we already have, and an idea of what a program can do.
That brings me to an idea I had this past week. I’m sure some of you have begun to write software that originated in your own heads. I would love to take a look at what you’re doing. If you’ve created any software with AppleScript by using the concepts taught in this column, please send me a copy of it. I’m very interested in what you’re doing with this knowledge.
I mentioned last month that we’ve pretty much covered everything you need to know to write software with very simple user interfaces. But programmers are always looking for ways to make their jobs easier, and one of the tools they’ve come up with is Object Oriented Programming, or OOP.
Object oriented programming exists in contrast to a traditional approach called procedural programming. Procedural programming is what we’ve been using so far, writing software that contains data in variables and writing handlers, or subroutines, that manipulate the data. Procedural programming took software a long way from the original days of punch cards, but as programs grew more and more complex, reaching millions of lines of code, it became difficult to maintain the software, and OOP was created as a tool for helping with this problem.
Procedural programming has two basic entities: variables and handlers (to use the AppleScript language term for functions). Handlers usually exist for the purpose of manipulating some variables in a very specific way. So you might have a variable that holds a list of names called nameList
and a handler that accepts a list of names and returns it sorted alphabetically, perhaps named SortNameList( nameList )
. Perhaps there’s another handler for adding a name to the list, and another one for removing a name from the list. There might be dozens of handlers, each one performing a specific action on a list. But all of these handlers are separate from that variable nameList
.
OOP combines these separate entities into a single new entity called an object. An object is a collection of data and routines to operate on that data. The data in an object are called properties, so rather than referencing the variable nameList
itself, we would reference the nameList
property of an object. The handlers associated with an object are called methods, and to call a method, we send a message to the object.
The advantage of objects is primarily portability. After we’ve created an object and have tested it thoroughly and have ensured that it works as we expect, we can take that object to other programs and have all of its functionality in the new software. This may sound much like one of the advantages of handlers themselves, but there is a major difference. If we create only handlers (outside of any object) and bring those handlers over to the new software, we haven’t brought any of the variables or data that the handlers expect. By placing everything in an object, we’re able to guarantee that everything we need is in the new software simply by copying and pasting the object’s code into the new program, or better, by loading the object from a shared library.
AppleScript doesn’t include all of the concepts of OOP, or at least not as much as languages such as Objective-C or C++. If you’re familiar with these languages at all, you’ll find that AppleScript doesn’t support the concept of private and public properties or handlers. All such items in a script object are public. But you can still use the concepts of OOP with AppleScript. AppleScript calls objects script objects, as you’ll see when we begin creating objects.
In one sense, you’re already familiar with objects. AppleScript comes with many different types of data built into the language, such as numbers and strings, and these data types have operations that can be performed on them. The ability to create our own objects gives us a great deal of flexibility in creating our own data types not built into AppleScript, providing the operations that can be performed as we see fit. Later on we’re going to create an angle object and define the operations that an angle should have. Then we’ll be able to use the angle object later on, whenever we need that type of data for our software.
Just as creating a handler without calling it won’t do anything, creating a script object without creating an instance of it won’t do anything either. An object is actually sort of like a template or blueprint for instances of the object. You can create as many instances as you like from a single object definition, just as you can create many houses from a single blueprint.
One last word before we delve into some examples: You don’t need to know OOP to create programs. You can create any program you want with just the principles of procedural programming, and for small projects, this will probably make sense. But once your software becomes more complex (and especially once you begin building graphic interfaces to your software) OOP can greatly simplify the programming process. Also, if you’re using Mac OS X and want to build programs with AppleScript Studio, or if you’re using Mac OS 9 and want to build programs with FaceSpan, an understanding of OOP is integral to both of these environments.
OK, so let’s build an object. The first object we’re going to create in AppleScript doesn’t really do anything except demonstrate how to create an object with AppleScript code.
on run -- Create a script object called MyScript script MyObject -- MyScript has a single property, myNumber property myNumber : 0 on ReportMyNumber() display dialog myNumber end ReportMyNumber end script -- MyObject end run
Like the program we created last month with a handler that isn’t called, this program when run won’t actually do anything, or at least nothing visible. It only declares a very simple script object called MyObject
. MyObject
has a single property called myNumber
which is initialized to 0. MyObject
also has a single method called ReportMyNumber
that displays the contents of the myNumber
property.
I said that the program doesn’t do anything visible. What it does do is create an instance of the object defined. If you run this program and view the result window (or result tab in Script Editor 2, recently released as a preview), you’ll see that the result of running this script is “script MyObject”
. Whenever a handler that contains a script object is executed, an instance of the script object is created. However, I’ve found that working with objects in AppleScript is easier if you use a handler called something like makeMyObject
to contain the script object and use code within the handler to return the object to the calling portion of the program. You’ll see this demonstrated later.
Note the exception to the “no handler within a handler” rule here. We have the run
handler, and inside of it is a script object which then has another handler. If the deeper handler is within a script object, then it can (and in fact, must) be contained within another handler somewhere. In fact, we could create another script object within MyObject
and have more handlers within it, although I’ve never actually seen this done.
It doesn’t make much sense to create a script object and then not use it, so let’s create an explicit instance of the MyObject
object and use its ReportMyNumber
method to find out the value of the MyNumber
property.
on run -- Create a script object called MyScript script MyObject -- MyScript has a single property, myNumber property myNumber : 0 on ReportMyNumber() display dialog myNumber end ReportMyNumber end script -- MyObject copy MyObject to MyInstance tell MyInstance to ReportMyNumber() end run
All we’ve added to the program are two lines. The first new line creates an instance of MyObject
, naming that instance MyInstance
. Then we use the ReportMyNumber()
method of MyInstance
. Running this program will display a dialog with a “0” in it, which is what the myNumber
property is equal to.
That last line could also have been written in a number of other ways. The first alternative is MyInstance's ReportMyNumber()
and the second is ReportMyNumber() of MyInstane
. Both of these are equivalent to tell MyInstance to ReportMyNumber()
, and as with many things in AppleScript, the way you call methods in an instance is up to you. I prefer the tell
version in most cases, although when the object’s method returns a value I’ll often use one of the other forms.
We could also have gotten the property directly without going through the object’s method.
on run -- Create a script object called MyScript script MyObject -- MyScript has a single property, myNumber property myNumber : 0 on ReportMyNumber() display dialog myNumber end ReportMyNumber end script -- MyObject copy MyObject to MyInstance display dialog myNumber of MyInstance end run
Many OOP languages let you disallow this kind of statement by making a property private (accessible only within the script object’s handlers), requiring that the rest of the program use the provided methods to access the properties. This is known as encapsulation, and although AppleScript doesn’t have this ability, you can always act as if it does by using methods to access property values, and this is the path that I recommend. That way if you change how myNumber
is stored within the script object but still allow the handlers of the script object to be called in the same way, the rest of your program won’t have a problem. For instance, perhaps we actually make myNumber
a list instead of a number, and we rewrite the ReportMyNumber()
handler to report the contents of the first item in the list. To the world outside of the object, everything works as before, but only if we use the object’s methods to access the properties rather than referencing them directly.
Now that we have talked about the basics of creating and using script objects, we’re going to create a more complicated object with a number of methods. Our object is actually going to be an angle. An angle only has one property, but there are a number of methods that would be useful to have as well. For example, adding angles works differently than if we simply use a number variable. For instance, it doesn’t make much sense (for my purposes anyway) to speak of angles greater than 360 degrees or less than 0, so my handlers for adding or subtracting two angles will work appropriately. Also, an angle can be measured in multiple ways, so we’ll use a different property to track each unit. In the same manner that we started with a simple version of our Sum Numbers program, we’re going to build this object from rather simple to much more complex. As we edit this script, I’ll place the new or changed code in italics.
Here’s a first draft of our angle script object.
-- Demonstration of AppleScript's object oriented techniques -- using an Angle as the sample object. -- Written by Charles E. Ross, 12/26/02 -- Version 1.0v1 on run -- Declare a script object called Angle. script Angle -- Angle has one property, the measurement of the angle in degrees. -- The size of the angle in other units is derived from the degrees. property degrees : 0.0 -- Sets the degrees property to the value passed to it. on SetSize(theSize) set degrees to theSize end SetSize -- Returns the value of the degrees property. on GetSize() return degrees end GetSize end script -- Create two instances of an angle. copy Angle to firstAngle copy Angle to secondAngle -- Set the values for our two angles. tell firstAngle to SetSize(180) tell secondAngle to SetSize(270) -- Get the sizes of our angles. display dialog "firstAngle has a size of " & ¬ firstAngle's GetSize() & " degrees." display dialog "secondAngle has a size of " & ¬ secondAngle's GetSize() & " degrees." end run
As you can see, this first version of the Angle
object is very simple. We have only one property, degrees
, and two handlers, SetSize()
and GetSize()
. Outside of the Angle
definition we create two instances of the Angle
object, set the size of both of them, and then report the size of both of them in a couple of dialog boxes.
Notice how we access the handlers of the Angle
instances. When we initialize the instances, we use a tell
statement to send a SetSize()
message to each of them. When we’re reporting the size of the angles, we use the possessive form to insert the sizes into a display dialog
statement.
This brings up an important point regarding the handlers that exist within objects. Our Angle
object has a GetSize()
handler. But we could also include a GetSize()
handler outside of the object which does something entirely independent of the handler within the object. Take a look at the following program.
-- Demonstration of AppleScript's object oriented techniques -- using an Angle as the sample object. -- Written by Charles E. Ross, 12/26/02 -- Version 1.0v2 on run -- Declare a script object called Angle. script Angle -- Angle has one property, the measurement of the angle in degrees. -- The size of the angle in other units is derived from the degrees. property degrees : 0.0 -- Sets the degrees property to the value passed to it. on SetSize(theSize) set degrees to theSize end SetSize -- Returns the value of the degrees property. on GetSize() return degrees end GetSize end script -- Create two instances of an angle. copy Angle to firstAngle copy Angle to secondAngle -- Set the values for our two angles. tell firstAngle to SetSize(180) tell secondAngle to SetSize(270) -- Get the sizes of our angles. display dialog "firstAngle has a size of " & ¬ firstAngle's GetSize() & " degrees." display dialog "secondAngle has a size of " & ¬ secondAngle's GetSize() & " degrees." GetSize() end run on GetSize() display dialog "The size is 42." end GetSize
Now perhaps you understand why we have to specify the object that a particular handler belongs to. This program operates the same as the last one, but after the two dialog boxes appear showing us the size of our two angles, we get another dialog box that says “The size is 42.” That last line of the run
handler isn’t attached to an instance of the Angle
object, so the handler that gets called is the one outside of any instance.
Note how we are creating the instances of Angle
: by using the copy
command. We are using copy
so that we are creating two separate instances of Angle
. If we had used set
as in set firstAngle to Angle
and set secondAngle to Angle
, we would not actually have two instances of the Angle
object. We would instead have one instance that could be referred to by two names. Go ahead and edit your script to use set
instead of copy
, changing nothing else in the script. You’ll get two dialog boxes reporting that both firstAngle
and secondAngle
are 270 degrees. That call to tell secondAngle to SetSize(270)
is setting the size of the same Angle
referred to by firstAngle
.
Let’s expand our Angle
object a bit. I’m sure all of you are familiar with using degrees to measure angles, but there are other units available. Mathematicians often use radians or gradians to measure angles. While there are 360 degrees in a full circle, there are 2* radians (about 6.28) and 400 gradians. Radians are useful when calculating the area of an arc (a section of a circle). Gradians come in handy because right angles have a size of 100 gradians, which is a nice round power of 10. Radians are usually expressed as a number times , such as 2 or /3.
We’re going to add to our Angle
object the ability to report the size in any of these units, including how much we should multiply by when the angle is expressed in radians, although we’re still going to store the size internally in degrees. Here’s the updated version.
-- Demonstration of AppleScript's object oriented techniques -- using an Angle as the sample object. -- Written by Charles E. Ross, 12/26/02 -- Version 1.0v3 on run -- Declare a script object called Angle. script Angle -- Angle has one property, the measurement of the angle in degrees. -- The size of the angle in other units is derived from the degrees. property degrees : 0.0 -- Sets the degrees property to the value passed to it. on SetDegrees(theSize) set degrees to theSize end SetDegrees -- Returns the value of the degrees property. on GetDegrees() return degrees end GetDegrees -- Sets the size of the angle by converting from radians, -- since the internal structure of the angle is stored -- in degrees. on SetRadians(theSize) set degrees to theSize * 180 / pi end SetRadians -- Returns the size of the angle in radians by converting -- from degrees. on GetRadians() return degrees * pi / 180 end GetRadians -- Sets the size of the angle by converting from radians -- divided by pi, since the internal structure of the angle -- is stored in degrees. on SetRadiansByPi(theSize) set degrees to theSize * 180 end SetRadiansByPi -- Returns the size of the angle in radians divided -- by pi by converting from degrees. on GetRadiansByPi() return degrees / 180 end GetRadiansByPi -- Sets the size of the angle by converting from gradians, -- since the internal structure of the angle is stored -- in degrees. on SetGradians(theSize) set degrees to theSize * 9 / 10 end SetGradians -- Returns the size of the angle in gradians by converting -- from degrees. on GetGradians() return degrees * 10 / 9 end GetGradians end script -- Create an instance of an angle. set firstAngle to Angle -- Set the values for our angle. tell firstAngle to SetDegrees(45) -- Get the sizes of our angles. display dialog "firstAngle has a size of " & ¬ firstAngle's GetDegrees() & " degrees, " & ¬ firstAngle's GetRadians() & " radians, " & ¬ firstAngle's GetRadiansByPi() & "pi radians, " & ¬ firstAngle's GetGradians() & " gradians." end run
Notice that the angle’s size is still stored internally in degrees, although the user of this object has the ability to get the size in any of the units desired.
This script also introduces the pi
variable in AppleScript, which is equal to the we’re familiar with. Notice that I didn’t say “the pi
constant.” pi
in AppleScript is a variable, and you could in fact change its value with a set pi to 3
or something similar.
The current way that a new Angle
object is created is somewhat awkward. We’re going to make it a bit easier to read exactly what is happening by enclosing the entire script object within a handler. Our new handler, MakeAngle()
will take one parameter, the size of the angle in degrees, and return an Angle
object. We’re also going to add some error checking to make sure that the size passed to our new handler is in fact a number and that the units passed are one of the four possibilities.
-- Demonstration of AppleScript's object oriented techniques -- using an Angle as the sample object. -- Written by Charles E. Ross, 12/26/02 -- Version 1.0v4 on MakeAngle(theSize, theUnits) -- Declare a script object called Angle. script Angle -- Angle has one property, the measurement of the angle in degrees. -- The size of the angle in other units is derived from the degrees. property degrees : theSize -- Sets the degrees property to the value passed to it. on SetDegrees(theSize) set degrees to theSize end SetDegrees -- Returns the value of the degrees property. on GetDegrees() return degrees end GetDegrees -- Sets the size of the angle by converting from radians, -- since the internal structure of the angle is stored -- in degrees. on SetRadians(theSize) set degrees to theSize * 180 / pi end SetRadians -- Returns the size of the angle in radians by converting -- from degrees. on GetRadians() return degrees * pi / 180 end GetRadians -- Sets the size of the angle by converting from radians -- divided by pi, since the internal structure of the angle -- is stored in degrees. on SetRadiansByPi(theSize) set degrees to theSize * 180 end SetRadiansByPi -- Returns the size of the angle in radians divided -- by pi by converting from degrees. on GetRadiansByPi() return degrees / 180 end GetRadiansByPi -- Sets the size of the angle by converting from gradians, -- since the internal structure of the angle is stored -- in degrees. on SetGradians(theSize) set degrees to theSize * 9 / 10 end SetGradians -- Returns the size of the angle in gradians by converting -- from degrees. on GetGradians() return degrees * 10 / 9 end GetGradians end script if class of theSize is not in {real, integer} then error "Invalid size" number 101 else if theUnits is not in ¬ {"degrees", "radians", "gradians", "radians by pi"} then error "Invalid units" number 102 else if theUnits is not "degrees" then if theUnits is "radians" then tell Angle to SetRadians(theSize) else if theUnits is "gradians" then tell Angle to SetGradians(theSize) else tell Angle to SetRadiansByPi(theSize) end if -- theUnits is "radians" end if -- class of theSize is not in {real, integer} then return Angle end MakeAngle on run set newAngle to MakeAngle(30, "degrees") display dialog "The angle has a size of " & ¬ GetRadians() of newAngle & " radians." end run
What we’ve done here is change the way the programmer creates new angles. We’ve taken the Angle
object out of the run
handler and placed it in a new MakeAngle()
handler. When that handler gets called from the run
handler, the Angle
object gets initialized automatically. Then the code in the rest of the handler is run, which does some error checking to make sure that the size and units are sensible. If there’s problem with either of the parameters, rather than return a value that’s invalid, we call an error, providing an error message and a custom error number. If you’d like to see how this would work, change the first line in the run
handler to read set newAngle to MakeAngle(30, "deg")
.
What else can we do to improve our Angle
object? Well, although strictly speaking, an angle can have any measurement, positive or negative, or even greater than 360, for our purposes, we’re going to make sure that, although such numbers can be passed to the MakeAngle()
handler, internally the angle will always be stored such that its size is between 0 and 360 degrees. If a negative value is passed to MakeAngle()
, we will add 360 degrees to the size until the value is above zero. If a size greater than 360 degrees is passed, we’ll perform a modulus operation.
AppleScript has a mod
operator which takes the remainder of one number divided into another, so, for instance, 13 mod 6
is 1, because 13 divided by 6 is 2 with a remainder of 1. Similarly, if MakeAngle()
is pass a value of 1000 degrees, this is equivalent to 280 degrees.
To ensure that the size of our angle is always between 0 and 360, we’re going to add a new handler called NormalizeSize()
, which will bring the angle within the range we want.
-- Demonstration of AppleScript's object oriented techniques -- using an Angle as the sample object. -- Written by Charles E. Ross, 12/26/02 -- Version 1.0v4 on MakeAngle(theSize, theUnits) -- Declare a script object called Angle. script Angle -- Angle has one property, the measurement of the angle in degrees. -- The size of the angle in other units is derived from the degrees. property degrees : theSize -- Sets the degrees property to the value passed to it. on SetDegrees(theSize) set degrees to theSize NormalizeSize() end SetDegrees -- Returns the value of the degrees property. on GetDegrees() return degrees end GetDegrees -- Sets the size of the angle by converting from radians, -- since the internal structure of the angle is stored -- in degrees. on SetRadians(theSize) set degrees to theSize * 180 / pi NormalizeSize() end SetRadians -- Returns the size of the angle in radians by converting -- from degrees. on GetRadians() return degrees * pi / 180 end GetRadians -- Sets the size of the angle by converting from radians -- divided by pi, since the internal structure of the angle -- is stored in degrees. on SetRadiansByPi(theSize) set degrees to theSize * 180 NormalizeSize() end SetRadiansByPi -- Returns the size of the angle in radians divided -- by pi by converting from degrees. on GetRadiansByPi() return degrees / 180 end GetRadiansByPi -- Sets the size of the angle by converting from gradians, -- since the internal structure of the angle is stored -- in degrees. on SetGradians(theSize) set degrees to theSize * 9 / 10 NormalizeSize() end SetGradians -- Returns the size of the angle in gradians by converting -- from degrees. on GetGradians() return degrees * 10 / 9 end GetGradians -- Ensures that the angle is between 0 and 360 degrees. on NormalizeSize() -- If the angle is negative, add 360 to it until -- it is positive. repeat until degrees >== 0 set degrees to degrees + 360 end repeat -- Perform a modulus operation on degrees to ensure that -- the size is less than 360. set degrees to degrees mod 360 end NormalizeSize end script if class of theSize is not in {real, integer} then error "Invalid size" number 101 else if theUnits is not in ¬ {"degrees", "radians", "gradians", "radians by pi"} then error "Invalid units" number 102 else if theUnits is "degrees" then tell Angle to NormalizeSize() else if theUnits is "radians" then tell Angle to SetRadians(theSize) else if theUnits is "gradians" then tell Angle to SetGradians(theSize) else tell Angle to SetRadiansByPi(theSize) end if -- class of theSize is not in {real, integer} then return Angle end MakeAngle on run set newAngle to MakeAngle(30, "degrees") display dialog "The angle has a size of " & ¬ GetDegrees() of newAngle & " degrees." end run
Notice that adding this new feature changes nothing about how the outside world creates angles using our object. Everything to the programmer using this object is the same as it was before. This is one of the great advantages of objects. So long as we keep the interface to the object the same, the internal workings of how things actually get done is irrelevant to the programmer using the object. One programmer can build the object and, making sure that the interface (i.e., the handlers) remains the same, can tweak it however needed. In fact, later on as we continue to edit this object, we’ll make a major design change to the object, but as far as programmers using the object are concerned, it will simply work a bit faster.
This month’s column has become quite long, but I hope you haven’t minded too much. Objects are a complex topic, but once you get your brain around how they work and what they can do for you, you’ll find all kinds of areas in your programs where you will want to use them.
Next month we’ll continue working on our Angle
object, adding more features. Then we’ll use it as the basis for another object, learning about another great advantage of object oriented programming: inheritance.
Also in This Series
- Getting the List of It—Part 2 · July 2003
- Getting the List of It · June 2003
- The Object of Programming—Part 2 · March 2003
- The Object of Programming · February 2003
- How to Handle Anything · January 2003
- Try to Handle This · December 2002
- Charting Your Success · September 2002
- Go with the Flow · August 2002
- Variables and Data · June 2002
- The Ultimate Customization · May 2002
- Complete Archive
Reader Comments (8)
Every language's syntax is strange when compared to another language. In fact, the syntax of Perl, Python, shell scripting, Pascal, BASIC and Lisp are all going to be strange to someone who has done the majority of their programming in C/Java/C++/Objective-C. This has no relevancy on the strength of weakness of these languages.
AppleScript doesn't need to change its syntax to be more like C, just as no other language does. AppleScript isn't meant to be a C derivative. Although it's grown beyond its original purpose, that purpose (an easy-to-learn English syntax that can be used to control other applications) still exists.
To decide that you aren't going to use a language because its syntax isn't familiar is rather shortsighted. While AppleScript isn't the best tool for every programming task, nor is any other language. The best option is to know a variety of languages so that you can use the best one for the job at hand. Were I to write a program that depended on speed a great deal, I wouldn't use AppleScript but would probably go with Objective-C. However, with AppleScript, I can build a tool in an hour to get a small job done. Using C or some C derivative (I'm familiar with C, C++ and Objective-C, but not Java) would take me three times as long at least.
Chuck
Thank you very much for your kind comments. Given that this column is written without monetary compensation, the gratifying words of the readers is quite welcome.
Chuck
Having said all this--great article. Thanks!
For example, I have a file containing a series of tasks. I want to read the file and execute the tasks. This seems a pretty straightforward procedural problem to me. Is there any benefit in using an OO approach?
David
Almost 10 years later it is still the clearest intro to OOP that I've ever seen
Add A Comment