![]() |
|
Published 1997-11-01 Printer-friendly version
In the last article, we discussed some OOP basics. If you thought I cut it off right when it was getting interesting, I did - for a very good reason. While we were leisurely going about OOP basics and defining key words, the learning curve was relatively flat. Fasten your seatbelts, as we leave the takeoff roll and enter a steep climb. Not to worry, we are going to have some fun too!
For the next logical step, I thought it would be fun to cover some of the new things in Clarion 4 while at the same time learning more new concepts. For this discussion, you might want to have the previous article at your side to refer to, as we will be using the words defined in that discussion (plus, we will get to a new OOP term shortly).
Ed. Note: Russell Eggen's previous article was "Why OOP", which appeared in Volume 1, Issue 1.
You may already use some OOP-like things. Let's take the Clarion language statement OPEN. Open what? How many things can this statement identify? EverOpen(File)? How about Open(Window) or Open(Report)? We could define it something like this (keeping it simple):
MAP Open PROCEDURE(File) END
We can assume that the procedure Open does something useful, like opening a file. What if we want to open something else, not a file, like a Window? Certainly, that is something we would want to do in a business program. Consider this code:
MAP Open PROCEDURE(File) Open PROCEDURE(Window) Open PROCEDURE(Report) Open PROCEDURE(View) END
The above would be quite valid although redundant, as this exists in the runtime library. In Clarion, this is called function overloading. This means that the parameters used (and type), depends on what action is taken to what entity. While we may simply use the key word open; we do not need to concern ourselves with what we are opening.
I now have to make a sudden left turn. This brings us to a relatively new term composed of poly, a prefix meaning much, many, and the suffix -morphism denoting the state of having a special form. Putting these two words together we get polymorphism, meaning having many forms. It does not mean that your parrot has a drug problem. The meaning is really straightforward and is the one used in OOP lingo.
In programming terms, this means that a method can have one name that is shared throughout the object type hierarchy. It can (and usually does) have different implementations for the method. Really fits into the OOP concept, doesn't it?
This is not the only type of polymorphism we support (more on this in a bit). To quote from the Clarion for Windows Programmer's Guide, "Polymorphism in object-oriented parlance is more commonly taken to mean the ability of a base class method to call methods of classes derived from the base class - without knowing at compile time exactly what method is actually going to be called."
Say what?! How can a base class do this? Are you pulling my leg?!
Well, lets start with some basics and build up to the point where anyone can understand what is happening here.
One could imagine that this could cover quite a lot of territory and it does. Both the Programmer's Guide and the book, "The Tao of Objects" describe how this works. To really take advantage of this, it may be worth a bit of study to discover how this works (in plain English please!)
In programming languages, every object created uses static linked methods. This means that the compiler resolves all references to procedures and functions by the time the program is loaded. In other words, the compiler decides what to call at compile time. This is known as early binding. In our example using the OPEN function, the compiler saw during compile time that you use several different methods with the same name. What is different is the type of parameter used. It knows what a File is (data-wise). Overloaded functions are statically linked. What makes this possible is that the compiler can tell the difference between them using the parameter as a guide.
With polymorphism, it is the object that determines which method to use. This means that the compiler must generate code to resolve these references. This is called dynamic or late binding.
OK, you head scratchers, you are probably wondering how we do this (after all, it does go against our training, right?). Let's take a closer look.
With something that can be early bound, the compiler can resolve this by assigning a procedure call to a specific code address in the executable. This is a direct call to the procedure. There is nothing wrong with that, even with OOP.
With late binding, the compiler, at compile time, creates something called a Virtual Method Table (VMT). This contains all the code addresses of all the virtual methods.
Whoa! Time out! What is a virtual method?Good question. Let's quickly define this. The definition of Virtual, in this instance, actually comes from the field of optics. It means noting an image formed that is not real, as an image in a mirror. Remember those Hollywood movies and computer magazines that use the words "virtual reality"? In this case, it means something that looks real, but in effect does not really exist.
In the last article, we discussed inheritance. This is the derived class being able to call "down" or "back" to a base class. A virtual method is the ability to "call up" or "out" to derived methods.
Hold the phone! How can a base class know what has been derived from it? It must be part of the Psychic Friends Network.
Not really. How does any program get a related record? It must look it up, right? Similar situation here. Remember the virtual method table we were discussing? This is simply a table that contains all the addresses for each method.
It places an entry in the VMT that has the address for this method, instead of generating an error. When the method is called (or used), your program then "looks up" the correct method by finding it first in the VMT. The VMT has the address for the method.
(I would also like to point out that if several virtual methods of the same name appear in the VMT at run time, the last one is called. More on this in a bit.)
This lets us use a base class to call a derived class to handle the differences. It will ignore the other differences for the same reason. In short, you can quickly get the proper method to get the job done, without extra coding. One may think that using the VMT is not very efficient and takes extra code to make the call. In most other languages, it does slow the application down. In Clarion, thanks to our wonderful development team, it requires just another instruction, so it is very fast!
For example, we may have a food class. There may be derived classes, such as steak, ice cream, and vegetables.The food class may have a method for serving. When you derive a steak class, it has its own serving method that is different from the ice cream method. Because the steak class was derived from the food class, it contains its own specific serve method. The steak and ice cream objects contain a common preparemethod. The prepare method calls the serve method, but it does not know, nor care, about the serve method for steak, ice cream or vegetables. It just tells the object to serve and it does.
The last article talked about using reference variables. One could use these as well, and there is nothing wrong with using them. However, it still requires some coding in your procedures.
Let's go back to our food class. In Clarion, we define an object using the CLASS statement like so:
Food CLASS,TYPE
Prepare PROCEDURE,VIRTUAL !Declare a Virtual method
Serve PROCEDURE,VIRTUAL !Declare a Virtual method
Construct PROCEDURE
Destruct PROCEDURE
END
OK, there is our object, sort of. Sort of? What are you doing here?
Lets explain a few things about this before we get into this further. The Virtual attribute tells the compiler that this will be placed in the VMT. The TYPE attribute simply means that we do not want to instantiate the object. In this case, we are assuming that I will instantiate the object later in code. If the TYPE attribute is left off, then the Class declaration declares both a type of object and an instance of that object. In Clarion, we can instantiate (a 10-dollar buzzword meaning "create") by 3 different means:
OK, I suppose that makes sense, but what is the Virtual thingy doing?
Good question.
In Clarion, a virtual method is one that is defined in both a base class and a derived class. It may or may not be real. This allows you to use either method as you see fit. The Virtual attribute must exist on both methods. This allows the base class to call "up" to a derived class.
Let's add some code to illustrate:
Food CLASS,TYPE
Prepare PROCEDURE,VIRTUAL
Serve PROCEDURE,VIRTUAL
Construct PROCEDURE
Destruct PROCEDURE
END
Steak CLASS(Food)
Prepare PROCEDURE,VIRTUAL
Serve PROCEDURE,VIRTUAL
END
CODE
Steak.Prepare !Call the Steak object's virtuals
Food.Prepare PROCEDURE CODE SELF.Prepare SELF.Serve
While we know that food can be prepared and served, a steak preparation and serving would be different than say ice cream. We know that if we use an ice cream method of prepare for a steak, it may not be the results we want as it may be served frozen in a bowl (with nuts and chocolate syrup - yuck!) We better make sure that we call the correct method! How?
Well, here is a bit of a pop-quiz: The first statement in the Food.Prepare method is a call to SELF.Prepare. Which method will execute? The answer is the Steak method. Why? Because the original call is to the Steak class. The base Food class is now calling "up" the Steak method. So, the actual method is Steak.Prepare.
OK, but what does "SELF" mean?
Very good question! The answer is very simple. As you can see from the above, one could get lost pretty easily. The "SELF" keyword means "whatever the current object is". It could be the parent, or something derived 64 levels down (not recommended you inherit that deeply!). Start to see the picture here?
Oh, I see! This Food.Prepare could be called and re-used depending on what I wanted to do with that object! The "Self" word makes that possible! I think I am starting to like OOP as I now see all sorts of code I do not have to write anymore but retain full functionality! Awesome!
Exactly! This is the whole premise behind OOP. Think not in linear fashion, but in concepts. Take for example a grinder. If you make an object for a grinder, what can you do with it? Would not coffee grind differently than, say, meat? However, the same object can be used for both! (If that is not clear to you at this point - if you don't mind, I will make my own cup of coffee!)
Caution: I would be a bit remiss if I do not point something out with this code. I used the above for conceptual purposes, but there is something inherently dangerous about it. Using SELF does leave you open for possible problems. Suppose, for example, I derived an IceCream class for which the Prepare method was not overridden. What would happen is that the SELF.Prepare would call Food.Prepare, which would call Food.Prepare, which would call Food.Prepare, and so on. This would give a stack overflow, which anything could happen (except for what you want). However, since this was overridden, it will work.
Now just a bit of a side trip here. Remember in the last issue about OOP, we talked about encapsulation (see Volume 1 Issue 1 for definition). While I just gave you enough to get the concept, there is a bit more to it. What if you define a class and you do not want other objects to know about it? There are many reasons to do this as you only want derived classes to know about it. In OOP circles, encapsulation is the practice of making objects and then hiding them from other objects.
We support this via the PRIVATE attribute. This means that the property or method is visible only to the other methods within that class. Without this attribute, any class can see it, use it and, if needed, override it. The Private attribute makes the methods or properties invisible to other parts of your program. Without it, the rest of your program can use it whenever it is instantiated and in scope.
However, what if you do not want to "restrict" your methods and properties that much, but do not want to share it with the rest of the program? Then use the PROTECTED attribute. This makes the methods and properties from a class visible to only those methods and properties that are derived from it.
Private methods and properties cannot be accessed from derived classes, but protected methods and properties can.
If you do not use Private or Protected, then it is considered public.
With Clarion 4, we use the new OOP templates, called 'ABC' or Application Builder Class. This is a new class of templates that uses many OOP concepts.
Special note: The 2003 templates (the Clarion class) that you are used to will also be part of Clarion 4, and you are free to use them. The only "catch" is that you cannot use the ABC and Clarion templates in the same application as they are shipped. There is a way to do this, but it is beyond the scope of this article and will not be discussed here.
One of the changes between the two is that the template variable names are changed. The conversion utility that will be provided does a wonderful job of resolving these name differences, even in embedded code! This makes the transition to the ABC class a breeze. The only real effort on the part of the programmer is to learn the new names.
For example, in 2003, find all the code that refreshes the Queue. Bits of code to successfully complete this task are stored in every browse procedure in your application (which has the side effect of making a large EXE).
In Clarion 4, this functionality is retained. However, since this is in a base class (from the viewpoint of your browse), your browse procedure is smaller as it only needs to call a method to refresh the Queue, for example. This has the nice side effect of making your code smaller. Remember, before Clarion 4, all code to do refreshing, thumb scrolls, etc, was in every browse procedure. For those of you who had more than one list box in their procedures, how big did those source modules get?
If the queue needs to be refreshed, here is the call:
BRWx.ResetQueue !x is a number
No mystery as to what is happening here as the name pretty much tells all. The point here is that instead of wading though lots of code to figure out what is happening, many of the old calls and procedures are being reduced to a simple naming convention that not only describes what is happening, but what is being affected as well. This reads like a "sentence". This is the real beauty of OOP. OOP code, (especially when done correctly), is very easy to read.
But if you plan to write OOP style, then coding is easier on you, as you only have to code the class, methods, etc. once to define them and how they work. This does require some planning and forethought on the programmer's part, but the payoff is huge!
Imagine simple looping though records in a child file that can be reduced to a single call. No more coding the same stuff over and over.
Let's use another example to really bring this home. Suppose that your application requires copying fields from one file to another. This is very common in accounting applications, as one needs to post transaction records to either a history file or, perhaps, the General Ledger application.
How is this done now? You may write code that assigns fields from the source file to the destination file and then ADD or PUT the destination records. Maybe, if you got lucky with your data model, you use the Clarion deep assignment. However, in my position of a Topspeed consultant this is rarely the case (and you independent consultants know what I am talking about).
Let's do this OOP style using some Clarion 4 objects! First let's declare the file structures (which is done via the dictionary, but we need to know the file structures for our discussion).
GenJrnl FILE,PRE(GJL),DRIVER('Topspeed'),CREATE
JrnlKey KEY(GJL:AcctNo,GJL:TrxNo),NOCASE,OPT
Record RECORD
AcctNo LONG
TrxNo SHORT
Reference STRING(25)
Amount DECIMAL(-10.2)
DatePosted LONG
END
END
ARTrx FILE,PRE(TRX),DRIVER('Topspeed'),CREATE
TrxKey KEY(TRX:AcctNo,TRX:TrxNo),NOCASE,OPT
Record RECORD
AcctNo LONG
TrxNo SHORT
Reference STRING(25)
Amount DECIMAL(-10.2)
DatePosted LONG
END
END
Ok, there are our file structures we want to play with. Now lets set up some code.
INCLUDE('ABUTIL.INC') !declare FieldPairs Class
Fields &FieldPairsClass !declare FieldPairs reference
CODE
Fields &= NEW(FieldPairsClass) !instantiate FieldPairs object
Fields.Init !initialize FieldPairs object
Fields.AddPair(GJL:AcctNo,TRX:AcctNo) !establish AcctNo pair
Fields.AddPair(GJL:TrxNo,TRX:TrxNo) !establish TrxNo pair
Fields.AddPair(GJL:Reference,TRX:Reference) !establish Reference pair
Fields.AddPair(GJL:Amount,TRX:Amount) !establish Amount pair
Fields.AddPair(GJL:DatePosted,TRX:DatePosted) !establish Date pair
FileManager.Fetch(ARTrx) !Read a record
Fields.AssignLeftToRight !copy from Trx FILE to GL File
IF Fields.Equal !compare the GL and Trx FILE
MESSAGE('Posting is successful')
FileManager.SaveFile(GenJrnl) !Save the copy
END
Fields.AssignRightToLeft !copy from GL to Trx FILE
PrintRegister !Print the register
Fields.Kill !terminate FieldPairs object
DISPOSE(Fields) !release memory allocated
What we did here was to show that with some simple statements one could get a record, assign it to another structure and then process the record. One could write other supporting code (like looping through the records), but the point here is to illustrate the concept using code that could be as close to real life as possible.
The FieldsPair class is new to Clarion 4 and used by the templates, but is part of the ABC classes. The great thing about the new templates is that the amount of code that you do not have to use. This makes your generation faster (less code to generate), compiles faster (same reason) and resultant smaller executables and libraries. It goes without saying that there is no loss of functionality and just the opposite effect has occurred. This has made your code easier to modify and maintain.
Can you imagine what would happen if you wrote templates to handle the very wordy SQL statements? One can take a cue from these templates and make their own classes for sending complex and verbose SQL statements to the database.
Due to the design of these templates, there is now a "no man's land" between the C4 templates and third party templates. Up to this point, the Development Center had to tread carefully when making changes to the templates, as it was very easy to break third party templates. With OOP, it is easy for third parties to override the default classes with their own, or define new ones. This means that both sides can now make modifications to the templates without all the worry that has been present in the past. This should expand on the third party market instead of hinder it.
With this and my previous article, I really have only scratched the surface and hopefully made OOP easier to understand and grasp. Remember that OOP is a style of coding. Due to this, we do not force it on anyone. You can use as much or as little as you like. It does use new words, which I have tried to define as we ran into them.
Despite its simple sounding name, OOP has its own body of data. While anyone can provide you data about it, true understanding must come from use. While I certainly understand that a new effort must be made to learn anything new, the learning curve is shortened when you have some data to play with. It has been said that OOP is a very simple concept that anyone can get in a flash (provided you spent 6 months getting to that flash point). I certainly hope that I have shortened that 6 months for you.
These two articles (see Volume 1, Issue 1 for the first), hopefully will put some sanity and stability to the obvious confusion that one gets when learning something new.
By all means, this series of articles is not everything there is to know about OOP (or our implementation of it), nor was this the intention. It came about from observation that many good coders got confused with it and almost every write up I have seen about it talked over people's heads. I have strived to not do this.
Richard Taylor has written up a very good discussion about OOP. This write-up comes from his talk at DevCon 97 and it would appear that this would be a chapter in the Programmer's guide for the Clarion 4 documentation. I strongly suggest reading this as he did a splendid job.
Ed. Note: Mike Hanson reported on Richard Taylor's talk, "Easing into OOP", in the DevCon Issue of Clarion Online.
I would like to know if these two articles were useful to you and what you got out of them. Please send me a message at reggen@topspeed.com and please CC the editor as well. I would like to know what you think.
Until next time..
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239