REALbasic University Resources:

RBU: Glossary Defines common REALbasic programming terms
  Archives Previously published columns
Translations: Dutch Courtesy of Floris van Sandwijk
  Japanese Courtesy of Kazuo Ishizuka
  Chinese Courtesy of Dong Li
  RBU Translation Guide Information on Translating RBU into other languages
Books: Matt's Book (2nd Edition!) Ideal for experienced programmers
  Erick's Book Best for beginning programmers
Websites: Mother Ship The publisher of REALbasic
  RB Webring Links to hundreds of REALbasic websites
  RESExcellence Another REALbasic programming column
  REALbasic Developer Magazine The premiere source for REALbasic instruction.

REALbasic University is Sponsored by

Make your Mac do what YOU want it to. Create games, utilities, cool Mac OS X tricks. Download REALbasic now and create your own software.


Print This Article

REALbasic University: Column 086

OOP University: Part Ten

Last time we used our knowledge of objects to create a simple drawing program. Today we're going to enhance that project into SuperDraw by taking further advantage of a concept known as Polymorphism.

Polymorphism

You may not have heard the term polymorphism, but you've already been using it! It's basically the concept we explored last time where a subclass can override its parent's methods. Overriding is the actual way polymorphism happens; polymorphism itself is the concept of different objects having the same methods doing different things.

Polymorphism is a powerful concept, but it's hard to grasp without concrete examples. That's why I wanted us to create SimpleDraw. Remember the draw method we added to shapeClass? Later we implemented different draw methods for each of shapeClass' subclasses, circleClass, rectClass, and triangleClass.

Polymorphism is what allowed us to do that: when the object being drawn is a triangle object, the triangleClass' draw method is called. But when the object is a circle, the circleClass' draw method is executed.

Without polymorphism this wouldn't work at all: we'd have to create a different kind "draw" command for each object, and then our calling code (the stuff in window1) would have to call the appropriate code for each kind of object. Remember how simple our drawing code was for canvas1's paint event?

  
for i = 1 to n
objectList(i).draw(g)
next // i

Without polymorphism it would have to be something like this:

  
for i = 1 to n
if objectList(i) isa circleClass then
objectList(i).drawCircle(g)
elseif objectList(i) isa rectClass then
objectList(i).drawRect(g)
elseif objectList(i) isa triangleClass then
objectList(i).drawTriangle(g)
end if
next // i

That really spoils the whole point of having objects that know how to draw themselves, doesn't it? It may not seem so bad with a handful of objects, but a full-featured drawing program has hundreds of kinds of objects and polymorphism is a requirement to making it possible to code all those quickly (you'll see a powerful practical example of this in the next lesson).

Encapsulation

Another key OOP concept is encapsulation. I've talked about it before, though in a roundabout manner. Encapsulation is the concept of separating object code from external code. In other words, a true object knows nothing of the world around it (except what it's told) and the world around it knows nothing about the internal workings of the object.

What's the benefit of encapsulation? Flexibility. Because the outside world knows nothing about what's going on inside of an object, changes to the object don't effect the outside code. Again, we need a practical example.

The most common use for encapsulation is protection of an object's data structure. Let's say you're creating a game. Pretend it's similar to the old "Where in the World is Carmen San Diego?" -- a "find the crook" game with extreme detail. You've got a country object: each country has properties like name, population, area, gross national product, etc. Each country also has some dynamic info like a list of cities along with data for each city. This info is dynamic because every country has a different number of cities and the data for each city varies.

When you first write this game, you use a simple array to hold each country's list of cities. Something like this:

  
Custom class cityClass:
cityname as string
population as integer
latitude as string
longitude as string

cityList(0) as cityClass

This looks good and seems to work. When your game initializes, you load in tons of country and city data from files and dynamically create new country objects, initializing each with their individual city lists and unique city data.

As the game continues to be developed, however, you soon discover that your game is getting slower and slower. Why? You try to figure it out and through testing figure out that what's slowing things down is the frequent city lookups your program does. Some countries have thousands of cities. Because of the way you designed the city data structure, to find a particular city you must do a sequential search like this:

  
n = uBound(cityList)
for i = 1 to n
if cityList(i).cityname = findCity then
return i
end if
next // i

This routine returns an index to cityList that is linked to the city's data. All well and good, but a sequential search is inefficient. A handful of searches like this aren't too bad, but your program apparently does thousands of these many times throughout the game. Every time your program needs data about a city, it must first look up the index in the data structure. One slow search is magnified and now the whole program is slow. Your beta-testers are complaining. What are you going to do?

Simple, you think. Why not change your inefficient data structure to a more efficient one? You could write your own indexed structure, but REALbasic supports the dictionary object, a powerful structure that's automatically hash indexed for speed. (In short, a dictionary has a hash table, meaning that the data is organized in a particular manner so the dictionary can find the item you want extremely quickly, even if there are hundreds of thousands of items in it.)

A dictionary sounds perfect: just pass it the name of a city and it will return the item you're looking for within that country. Now your cityList definition looks like this:

  
cityList as dictionary

Because dictionaries store data as variant, you can store whatever you want inside, including a cityClass object. Perfect! You do some tests in a separate project and the speed difference is amazing: the dictionary method is anywhere from 10 to 150 times faster.

In Detail

On a fast computer the times we're talking about are tiny: a dictionary's worse time is half a millisecond while a sequential search of the same 5,000 items is 62 milliseconds. But do that search 50 times and you're talking three whole seconds, or magnify that list to 50,000 items and the dictionary is 1,384 times faster!

If you want to play with this example, you can grab a demo project file here. Change the size of the array in the code to try different array amounts and see how that effects search times.

However, once you change your country object to use a dictionary data structure instead of an array, you discover horrible problems. In dozens of places throughout your program you directly accessed the cityList array to do things to it. Now your program won't compile, and those routines are broken since a dictionary is completely different from an array.

If you'd written things in proper OOP fashion in the beginning, none of your external code would understand the cityList data structure. Instead, they would talk to cityList via predefined routines you provided, such as getDataField, setDataField, deleteDataField, countDataFields, etc. If you'd written your game that way, changing the data structure from and array to a dictionary wouldn't have broken anything: you'd just need to rewrite those routines that add, modify, and delete data and all the external code would have remained the same. You see how encapsulation saves you time?

You're going to see encapsulation at work within the SuperDraw project.

Creating SuperDraw

Now I'd promised we'd get to SuperDraw today, but unfortunately we're running out of time. We'll get it started today, however, and finish it over the next couple of lessons.

First, let's look at our goals with SuperDraw. What we want is to be able to select, move, resize, and delete the drawn objects. How would we go about doing that in an OOP manner?

When I first conceived of this example, I thought it would be simple to add this behavior to the existing objects I'd created. However, I soon ran into a little snag.

The problem is this: my custom objects, because they're not based on native REALbasic controls, don't know how to receive events like the refresh event. This means they don't get redrawn when needed, and that's bad.

Now there are two potential solutions to this problem. One, I could simply base shapeClass on a canvas or rectControl (a canvas is based on rectControl). In that way all the subobjects would inherit the events we need. However, if you try this and run the program, REALbasic generates an error: "Can't dynamically create controls from classes."

What does that mean? Well, for REALbasic to add controls dynamically (you'll remember we did that for the playing cards in RBU Pyramid), it needs a control in the actual window for it to base the new control on. You can't just instantiate a control from a class, only from an existing control. The simplest way to explain this is to show you some code:

Won't Work (invalid):

  
dim s as myEditFieldClass

// Doesn't work because we're basing the new
// control on a class, not on a existing control
s = new myEditFieldClass

Works (valid):

  
dim s as myEditFieldClass

// editField1 is of type myEditFieldClass and
// exists within the current window
s = new editField1

Does that make sense? Now while I know this works this way, I don't know why, specifically, REALbasic has this limitation, but unless that changes someday, we have to work around it.

So our second way around our redraw problem is to use an existing canvas object, which receives a paint event, and have it pass that event on to our custom objects.

While it turns out this is slightly more complicated initially, the end result is extremely elegant and powerful. What we do is create our own custom canvas object and have it know all about our drawing!

In SimpleDraw we had window1 maintaining our drawing's data structure. In SuperDraw, we'll have our custom canvas class do that. This is actually much more object-oriented and encapsulated as window1 (our external code) doesn't have to know anything about our drawing data structure.

Let's get started. Add a new class to your SimpleDraw project file (File menu, "New Class"). Name the new class drawCanvasClass and give it a super of canvas.

Now we're going to move our data structure from window1 to drawCanvasClass. Open drawCanvasClass and add this property (Edit menu, "New Property"): objectList(0) as shapeClass. Delete the property from window1.

In Detail

Starting with REALbasic 4.5, you can hold down the Option key as you drag an item from one window to another to copy it. You can use this method to copy the objectList property of window1 to drawCanvasClass. Then just delete it from window1!

Now in our old program, we had code within addObjectButton that added objects to our drawing canvas. But now, since we're creating a custom canvas object, we'll put that code right within drawCanvasClass. So select all the code within the action event of addObjectButton and copy it to the clipboard.

Add a new method (Edit menu, "Add Method...") to drawCanvasClass like this:

Within this method, paste the old code. Now we just need to modify this code a bit. First, since we're now passing a our size parameters to this method, we don't need most of the variables we've declared. So simplify the dim statement to:

  
dim n as integer

Next, let's get rid of the uBound and rnd code blocks: we'll do this differently. Our code will begin with the select case statement, but instead of index, we'll have it check for kind (one of the parameters passed).

Then we'll change the objectList(n) = in the case statements to objectList.append commands.

In the code that follows we need to define n as uBound(objectList) (we had to wait until after the append command otherwise it wouldn't be current) and set the properties of our object to the passed parameters. Finally, since we're within a canvas, we'll change the redraw command (and we won't redraw the entire canvas, just the area of the current object, as that's more efficient).

The final, complete routine should look like this:

  
dim n as integer

// Create object
select case kind
case 0 // circle
objectList.append new circleClass
case 1 // rect
objectList.append new rectClass
case 2 // triangle
objectList.append new triangleClass
else
// Just in case
objectList.append new shapeClass
end select

n = uBound(objectList)
objectList(n).left = left
objectList(n).top = top
objectList(n).width = width
objectList(n).height = height

// Force this object to draw
refreshRect(left - 4, top - 4, width + 8, height + 8)

We're almost there, just a couple more steps.

Next, copy the code in canvas1's paint event and put it into the paint event of drawCanvasClass. You can delete the code from canvas1's paint event.

But of course right now we've just defined a class for our new canvas: to make that do anything, we must have an instance of it on our window. So select canvas1 and make its super drawCanvasClass instead of canvas.

Now we just have to change our addObjectButton code. Before had to do all the work of creating new objects: now it just tells our drawCanvasClass object to do that!

Here's the new, much simpler code for addObjectButton:

  
dim l, t, w, h as integer

// Generate random size and location
w = round(rnd * 20) + 10
h = round(rnd * 20) + 10
l = rnd * (canvas1.width - w)
t = rnd * (canvas1.height - h)

canvas1.addObject(index, l, t, w, h)

You should now be able to run SimpleDraw2 (I'm calling it that just to distinguish from last week's version) and it should work exactly as before.

I'm sure you're thinking that was a lot of work just to get back to where we started -- our program, from the user's perspective, doesn't do anything better!

But it is better underneath. Look at the code for window1. There's hardly any code there now! Compare it to the old version. Isn't the new way simpler and more elegant? Not only is the code for addObjectButton simpler, but now addObjectButton doesn't know anything about our data structure. We have successfully encapsulated our data structure! If we ever decide to change how our drawing data is stored, we can do that without breaking any of our external code.

Whew! That's it for this lesson. Next week we'll do some really cool stuff with those objects (making them selectable) and soon we'll have a remarkably powerful yet simple drawing environment. The cool part is that since we've created it the OOP way, we can easily reuse our drawing area in other programs -- something I have definite plans to do!

If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.

Next Week

We turn SimpleDraw into SuperDraw.

Subscribe to RBU Notify!

Some of you may not be aware, but Applelinks has established a notification list for REALbasic University. Subscribe today and you'll receive a quick email notifying you each time a lesson is published! It's a handy reminder to keep up with RBU.

Letters

No letters this week since the column was so long!


About the Column
REALbasic University is a weekly instructional column on programming with REALbasic and is brought to you by REALbasic Developer, the magazine for REALbasic programmers.

Each week we answer select reader questions, and we're always open to ideas for future columns. Send your questions to . (Keep your questions simple and specific. General queries like "How do I write my own web browser?" will be neglected.) Your question won't be answered immediately, but will be answered in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise it's fair game.)

About the Author
is an author, philosopher, graphic designer, photographer, film director, soccer fanatic, and programmer (among other things). He writes for MacOpinion, runs his own software company, Stone Table Software, which sells the revolutionary Z-Write word processor, and is Publisher and Editor of REALbasic Developer. He lives in Northern California with his cats, Mischief and Mayhem, and is rapidly running out of free time.

See the REALbasic University Archives


REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.

Email This Article - Comment On This Article

.

Reader Specials

Server Racks Online:
Apple Xserve CompatibleServer Racks and Universal Network Racks
42U KVM Switch Solutions:
High-End Mac and Multi-Platform KVM Matrix switching solutions!
Digital Camera Online:
Great prices on Digital Cameras and accessories!
KVM Switches Online:
Great prices on Mac KVM Switches from the leading manufacturers!
LCD Monitors Online:
Great prices on LCD Monitors from the leading manufacturers!
LCD Projectors Online:
Shop online for LCD Projectors from the leading manufacturers!
USB 2.0 Online:
Great prices on USB 2.0 products from the leading manufacturers

Serious Business Software:
Accounting, Sales, Inventory, CRM, Shipping, Payroll & more!

KVM Switch solutions for MACs:
DAXTEN is a KVM switch, KVM extender and monitor splitter specialist for PC, SUN and MAC applications from name brand manufacturers - offices worldwide.

The "Think Different Store: The iPod Accessories Store - iPod cases, iPod mini, iPod photo, speakers, itrip, inMotion, Soundstage and all other iPod accessories

Earn Cash with the ThinkDifferent Store Affiliates Program

Need A Web Site?
Applelinks Web Hosting Starting at 19.95 a Month

iTunes_RGB_9mm

.

iTunes_RGB_9mm

Cool Mac Gear


iPod 1G-2G
iPod 3G
iPod 4G
iPod Mini
PowerBook-iBook
Keyboard Skins
Garageband