| |||||||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||||||
Print This Article REALbasic University: Column 086
OOP University: Part TenLast 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.
PolymorphismYou 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?
Without polymorphism it would have to be something like this:
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).
EncapsulationAnother 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:
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:
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:
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.
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 SuperDrawNow 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):
Works (valid):
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.
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:
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:
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:
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 WeekWe 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.
LettersNo 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 See the REALbasic University Archives
REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.
| |||||||||||||||||||||||||||||||