- The Class Hierarchy
- Creating a New Class
- Declaration and Instantiation
- Constructors and Destructors
- Garbage Collection
- Object Operators
- Adding and Overriding Methods
- Calling the Overridden Method
- Access Scope: Public, Private, Protected
- Setting Properties with Methods
- Default and Optional Parameters
- Declaring Variables Static and Const
- Revisiting the StringParser Module
- Example: Creating a Properties Class
- Data-Oriented Classes and Visual Basic Data Types
- Advanced Techniques
In this section I will review some relatively more advanced programming techniques you can use to extend the functionality of your REALbasic classes.
Interfaces and Component-Oriented Programming
Interfaces are an alternative to subclassing. With subclassing, one class can be a subclass of another. Interfaces do not deal in subclasses. When talking about Interfaces, you say that a class implements a particular Interface.
Interfaces are another means by which you encapsulate your program. This is also one of those areas where the term is used differently in different programming languages. In REALbasic, an interface is used much in the same way that the term is used with Java. Other languages, such as Objective-C, refer to a similar concept as a protocol.
Nevertheless, when one class is a subclass of another, it shares all the same members with the parent class. Although the subclass may optionally reimplement or override a method from the parent class, it doesn't have to reimplement any method unless it wants to modify what that method does. The benefit to this, so the story goes, is that it enables code reuse. Simply put, you don't have to write any new methods for the subclass to get the functionality resident in the superclass.
There are some cases when you have two objects (or classes) that are related and that perform the same tasks, but they both do it in such a fundamentally different way that establishing the class/subclass relationship doesn't really buy you anything. At the same time, it's convenient to formally establish this relationship so that the different objects can be used in similar contexts. If two class share a common set of methods, it is possible to link these two classes together by saying they share a common interface. This task involves defining the interface itself and then modifying to classes to indicate that they both implement this common interface.
When this is the case, no code is shared. In the immediate sense, at least, code reuse isn't the object of this activity, but as you will see, it does make it possible to streamline the code that interacts with objects that share the same interface, which is why it can be so helpful.
When I talked about encapsulation, I used the fast-food restaurant drive-up window as an example. The fast-food restaurant I was thinking about when I wrote that example had three points of interaction for me: the first was the menu and speaker where I placed my order, the second was the first window, where I paid, and the third was the second window where I picked up my order. I said that the fast-food restaurant was encapsulated because I only had three touch points where I interacted with it, even though a whole lot happened between the time I placed my order and picked up my order.
Fast-food restaurants aren't the only places that use the drive-up window "interface." Banks use them, too. Even liquor stores in Texas do. At least they did when I lived in Texas—at that time the drinking age was 19, and as a 19-year-old living in Texas, I thought that drive-up liquor stores were a very clever idea. I know better than that now. But I digress.
The point is that fast-food restaurants, banks, and liquor stores really don't have all that much in common, other than the drive-up window. As a consumer of hamburgers, liquor, and money (for hamburgers and liquor), I go to each place for entirely different reasons, but there is an advantage to my knowing that all three locations have drive-up windows. From a practical perspective, it means that I can drive to all three locations and never have to get out of my car. As long as I am in my car, I can interact with things that have drive-up windows. I might drive to the bank for a little cash, then swing by the fast-food restaurant for a hamburger and cola, followed by a quick jaunt to the liquor store for an aperitif. Same car, same guy at all three locations.
In my own humble opinion, I think interfaces are underrated and underused in REALbasic. Whenever you read about object-oriented programming, you almost invariably read about classes and objects and inheritance and things like that. Based on my own experience, I used to try to squeeze everything into a class/subclass relationship, regardless of whether it made sense to do so, when I could more easily have implemented an interface and be left with a more flexible program. So my advice to you (painfully learned, I might add) is to give due consideration to interfaces when you are thinking about how to organize your program.
Interfaces seem to have higher visibility in the Java programming world. I really began to see and understand their usefulness when I worked with the Apache Cocoon content-management framework. At the heart of Cocoon is the Cocoon pipeline, and the pipeline represents a series of steps that a piece of content must take to be displayed in a web page, or written to a PDF file. Basically, there are three stages:
- The content must first be generated. Perhaps it resides as a file on the local file system, or it could live somewhere out on the Web, or it could even be stored in a database or some other dynamic repository.
- The content must then be converted or transformed into the format required for the particular task at hand. If you are using Cocoon to manage your website, you might take the source data and transform it into HTML.
- Finally, the content must be sent to the destination—either to a web browser at some distance location or into a file of a particular format to be stored on your computer.
In all three cases, the basic task is the same, but the specific steps taken within each stage are different, depending on the situation.
Cocoon uses a component-based approach; a component is basically an object that implements a particular interface. In the Cocoon world of components there are Generators, Transformers, and Serializers. These are not classes—they are interface definitions that say that any object that is a Generator is expected to behave in a certain way. There is a component manager that manages these components and shepherds the content through the pipeline as it goes from component to component.
The task or activity that a Generator must do is simple. It has to accept a request for content in the form of a URL, and it returns XML. That's it. Regardless of where the content comes from—a local file, a remote file, or a database, the same basic activity gets done. All the component manager knows about the Generator is that it can ask for a file at a given URL, and it can expect a file in return in XML format.
Now, if you are a programmer and you need to supply Cocoon with content, all you have to do is write an object that implements the interfaces specified by the Generator component and plug it into the pipeline. It's as simple as that. That's the beauty of components—you can mix and match them, plug them in, and take them out.
If you were to try the same thing using classes, you'd find your life to be extremely more complicated. You'd have to start with some abstract "Generator" class that had methods for requesting data and returning data as XML. Then you'd have to write a subclass for each kind of content you might want to retrieve and then override the associated methods. But what happens if you want to use a class as a generator that doesn't subclass your "Generator" class? Because REALbasic supports only single inheritance, each class can have only one superclass, and this imposes some limits on what you can do.
Interfaces, on the other, do not have the same limitations. A class can implement any number of interfaces, as long as it has the right methods.
Interfaces in REALbasic
To demonstrate how interfaces can be put to use in REALbasic, I'll define an Interface and then modify the Properties class to work with this interface. The interface will also be used extensively in the following section when I talk about how to create your own customized operator overloaders in REALbasic.
Before I get started, I should say that REALbasic uses interfaces extensively with Controls in a process called Control Binding. In the chapters that follow, I will revisit interfaces and show you how REALbasic uses them to make it easier to program your user interface.
If you recall, the Properties class implemented earlier in the chapter was designed to be able to accept both a string or a FolderItem in the Constructor. This is good because it makes it convenient to use, but it gets a little ugly when you get inside to look at the guts of the program. The reason I don't like it is that there are also two ParsePropertyFile methods, one for strings and the other for FolderItems, and each one is implemented differently. I don't like this because if I want to make a change in the future, I need to make sure that the change doesn't break either method. I'd rather have to check only one.
I will repeat them here to refresh your memory:
Protected Sub parsePropertyFile(myFile as string) Dim my_array(-1) as String Dim line as String my_array = myFile.split(Chr(13)) For Each line In my_array Me.parseLine(line) Next End Sub
This first version accepts a string in the parameter and then does two things. First, it splits the string into an Array and then it cycles through that Array to parse each individual line. Next, take a look at the FolderItem version:
Protected Sub parsePropertyFile(myPropertyFile as folderItem) Dim textInput as TextInputStream Dim astr as string If myPropertyFile Is Nil Then // An error has occurred Return End If If myPropertyFile.exists Then Me.file = myPropertyFile textInput = me.file.OpenAsTextFile Do astr=textInput.ReadLine() Me.parseLine(astr) Loop Until textInput.EOF textInput.Close Else // An error has occurred End If End Sub
This, too, can be divided into two basic steps. First, the file is opened and a TextInputStream is returned, and second, a loop cycles through the TextInputStream parsing each line in the file. A much better design would be to have the "looping" take place in one method, and the other prep work take place elsewhere. To do that, we need to find a common way to loop through these two sources of data so that they can be handled by one method.
The obvious and easiest-to-implement solution would be to get a string from the TextInputStream and then pass that string to the parsePropertyFile method that accepts strings. Unfortunately, that doesn't help me explain how to use interfaces, so I'm not going to do it that way. Instead, I'm going to implement an interface called ReadableByLine and then pass objects that implement the ReadableByLine interface to a new, unified parsePropertyFile method.
There are advantages to doing it this way—some of which are immediate, whereas others are advantageous later on when you want to do something else with this class. In fact, this interface will come in even handier in the next section when I am writing custom operators for the Properties class.
To implement the class interface, you first have to decide which methods will compose the interface. To make it the most useful, you want to use ones that will be applicable to the most situations. In the previous implementation of the parsePropertyFile methods, the loop that iterated over the TextInputStream is most promising. (Because Arrays are not really classes, the techniques used to iterate over the Array aren't applicable.) The methods are used by the TextInputStream class, and the code in question is this:
Do astr=textInput.ReadLine() Me.parseLine(astr) Loop Until textInput.EOF
If you look up TextInputStream in the language reference, you'll find that there are two implementations of Readline, and that EOF is a method. Their signatures follow:
TextInputStream.ReadLine() as String TextInputStream.ReadLine(Enc as TextEncoding) as String TextInputStream.EOF() as Boolean
There are other methods used by TextInputStream, but these are the ones I used previously. The language reference also says that the TextInputStream implements the Readable class interface. (The current language reference is in a state of flux as I write this, so I can only assume it still says this, but it's possible that it may not.) If REALbasic has already defined an interface, that's worth looking into. Readable is also implemented by BinaryStream, and the interface has two implementations of a Read method:
.Read(byteCount as Integer) as String .Read(byteCount as Integer, Enc as Encoding) as String
Rather than reading text line by line, it reads a certain number of bytes of data, which isn't what we need. Therefore, inspired by the Readable title, I'll create a ReadableByLine interface that can be implemented by any class that wants you to be able to read through it line by line.
You create a class interface just like you create a class or a module. In the same project that contains the Properties class, click the Add Class Interface button on the Project toolbar, and you'll be presented with an screen much like the one used for modules and classes. The primary difference is that all your options are grayed out except Add Method; that's because interfaces contain only methods.
All you do when creating an interface is define the signature of the method—you do not implement it in any way. To implement the ReadableByLine interface, we'll implement the ReadLine() and EOF() methods like those used by TextInputStream. I'll also add another called GetString(), the usefulness of which will become evident later.
.ReadLine() as String .ReadLine(Enc as TextEncoding) as String .EOF() as Boolean .GetString() as String
The next step is to go back to the Properties class and make the following modifications.
Implement the following parsePropertiesFile method:
Protected Sub parsePropertyFile(readable as ReadableByLine) Dim aString as String If Not (Readable Is Nil) Then Me.readableSource = readable Do aString=readable.readLine() me.parseLine(aString) Loop Until readable.EOF End if End Sub
Note that in the parameter, we refer to ReadableByLine exactly as if it were a class and readable was an instance of the class. Any variable that is declared an "instance" of ReadableByLine only has access to the methods of the interface, regardless of what the underlying class of the object is (which can be anything as long as it implements the ReadableByLine methods).
Next, the Constructors need to get updated. Instead of supplying a FolderItem or a string to the parsePropertyFile method, we want to send objects that implement the ReadableByLine interface.
The TextInputStream implements those methods. After all, that's where we got them from, but this also presents a problem. Because TextInputStream is a built-in class, I just can't go in and say that TextInputStream implements the ReadableByLine interface. REALbasic doesn't provide a mechanism for that.
The next possibility is to create a custom subclass of the TextInputStream and then say that it implements the interface, but that creates a problem, too. TextInputStream is one of a handful of built-in classes that can't be subclassed. Every time you use TextInputStream, you generate it from a FolderItem as a consequence of calling the OpenAsTextFile method. You can instantiate it yourself, or subclass it, but you can't assign any text to it (as far as I can tell), so it's only worthwhile when you get it from a FolderItem.
There's a third approach, which does mean you have to create a new class, but it's a good solution for these kinds of problems. All you do is create a class that's a wrapper of the TextInputStream class and say that it implements the ReadableByLine interface. This is a common tactic and it's sometimes called the "façade design pattern" in object-oriented circles because its one object provide a false front to another.
When you create the class, name it ReadableTextInputStream and add ReadableByLine to the interfaces label in the Properties pane.
The Constructor accepts a TextInputStream object and assigns it to the protected InputStream property.
Sub Constructor(tis as TextInputStream) InputStream = tis End Sub
The other methods call the associated method from the TextInputStream class. They also append the value to the SourceLines() Array, which is primarily a convenience. It also provides a way to get at the data after you have iterated through all the "ReadLines" of TextInputStream.
Function ReadLine() As String Dim s as String s = InputStream.ReadLine Me.SourceLines.Append(s) Return s End Function Function ReadLine(Enc as TextEncoding) As String Dim s as String s = InputStream.ReadLine(Enc) Me.SourceLines.Append(s) Return s End Function Function EOF() As Boolean Return InputStream.EOF End Function Function getString() As String Return Join(SourceLines, EndOfLine.UNIX) End Function
The final getString() method provides a way to get a string version of the TextInputStream.
Protected InputStream As TextInputStream SourceLines(-1) As String
Now that the TextInputStream problem is solved, something similar needs to be done for strings.
The string to be read is passed to the Constructor. Because I will be reading the string line by line, the first step is to "normalize" the character used to signify the end of a line. The ReplaceLineEndings function does that for me, and I have opted to standardize on Unix line endings, which are the ASCII character 13, otherwise known as the newline character.
Then I use the familiar Split function to turn the string into an Array. Finally, I will need to be able to track my position in the Array when using ReadLine so that I will know when I come to the end. I initialize those values in the Constructor. LastPosition refers to the last item in the Array. The ReadPosition is the current item in the Array being accessed, and it starts at zero. It will be incremented with each call to ReadLine until all lines have been read.
Sub Constructor(aSource as String) Source = ReplaceLineEndings(aSource, EndOfLine.UNIX) SourceLines = Split(Source, EndOfLine.UNIX) LastPosition = Ubound(SourceLines) ReadPosition = 0 End Sub
If you recall, the Do...Loop we used to iterate through the ReadLines() of the TextInputStream tested to see if it had reached the end of the file after each loop. The EOF() function provides for this functionality and will return true if the ReadPosition is larger than the LastPosition.
Function EOF() As Boolean If ReadPosition > LastPosition Then Return True Else Return False End if End Function
In the ReadLine() methods, the line is accessed using the ReadPosition prior to incrementing the ReadPosition. The line text is assigned to the s variable. After that, ReadPosition is incremented, then s is returned. ReadPosition is incremented after accessing the Array because the EOF test comes at the end of the loop. This is also the way we check to see if ReadPosition is greater than LastPosition, because after you have read the last line and incremented ReadPosition, it will be equal to LastPosition + 1.
Function ReadLine() As String Dim s as String s = SourceLines(ReadPosition) ReadPosition = ReadPosition + 1 Return s End Function Function ReadLine(Enc as TextEncoding) As String // Not implemented; Return ReadLine() End Function
Because I start with a string, the getString() method is only a matter of returning it. The Source property is assigned the passed string in the Constructor.
Function getString() As String Return Source End Function
ReadPosition As Integer Encoding As TextEncoding Source As String SourceLines(-1) As String LastPosition As Integer
Finally, the two constructors for the Properties class need to be modified as follows:
Sub Properties.Constructor(aPropertyFile as FolderItem) Dim readable as ReadableTextInputStream readable = new _ ReadableTextInputStream(myPropertyFile.OpenAsTextFile) parsePropertyFile readable End Sub Sub Properties.Constructor(myPropertyString as String) Dim readable As ReadableString readable = New ReadableString(myPropertyString) parsePropertyFile readable End Sub
For good measure, I create a Constructor that accepts a ReadableByLine implementing object directly.
Sub Properties.Constructor(readable as ReadableByLine) parsePropertyFile readable End Sub
This may seem like a lot of work to implement an interface, but it shows you a realistic example of how one might be used. Now that this groundwork is laid, it is easy to come up with additional sources of "properties." For example, you could store them in a database. A database cursor would adapt to the ReadableByLine interface easily, and it would take very little work to add that as a new source for key/value pairs.
In the next section it will come up again, and at that point you will be able to see how the interface provides a lot more flexibility as the class you are working on grows in functionality.
Custom Operator Overloading
The fact that you can use the + with integers and strings is an example of operator overloading that you have already encountered. Customizable operator overloading is a relatively new feature in REALbasic, and I think it's extremely powerful and fun to use. It creates a lot of flexibility for the developer, and it is a good thing. A very good thing.
To refresh your memory, an operator works like a function except that instead of arguments being passed to it, operators work with operands. There's usually an operand to the left and the right of the operator, and REALbasic determines which overloaded version of the operator to use, based upon the operand types. That's how REALbasic knows to treat 1+1 different from "One" + "Two".
Customized operator overloading is accomplished by implementing one or more of the following methods in your class. I'm going to use the Properties class as our operator overloading guinea pig and add a lot of new features that will allow you to do all kinds of lovely things with the class, including adding together two Properties objects, testing for equality between two different instances, coercing a Properties object into a string and so on.
The operators will also work with the ReadableByLine interface so that in addition to being able to add two Properties objects together, you can add an object that implements ReadableByLine to a Properties object, and so on.
Function Operator_Lookup(aMemberName as String) as Variant
Operator_Lookup() counts among my favorite operators to overload. I don't know why. I just think it's fun because it effectively allows you to define objects in a much more dynamic way by adding members to the object at runtime. You're not really adding members, but it gives the appearance of doing so. The Operator_Lookup() function overloads the "." operator, which is used when accessing a method or property of an object.
Here is an example from the Properties class:
Function Operator_Lookup(aKey as String) as String Return me.get(aKey) End Function
By implementing this function, I can now access any key in the Properties class as if it were a public property or method of the Properties class. This is how it would work in practice:
Dim prop as Properties Dim s as String prop = New Properties() prop.set("FirstKey") = "FirstValue" prop.set("SecondKey") = "SecondValue" s = prop.FirstKey // s equals "FirstValue"
As you can see in this example, I am able to access the "FirstKey" key using dot (".") notation. When you try to access a member that REALbasic doesn't recognize, it first turns to see if an Operator_Lookup() method has been implemented. If one has been implemented, it passes the member name to the Operator_Lookup() method and lets that function handle it any way it likes.
In the Properties example, it calls the get() function, which returns a value for the key or, if the key is not in the Properties object, returns an empty string. Basically, I have a Dictionary whose keys are accessible as if they were members of the object. If you are familiar with Python, this may sound familiar because Python objects are Dictionaries, and you can access all Python objects both through Python's Dictionary interface or as members of the Python class. I should say, however, that I would not recommend using a Dictionary subclass with the Operator_Lookup() overloaded method as a general replacement for custom objects because there is a lot of overhead when instantiating Dictionaries. It's a perfect solution for situations like the Properties class, which needs to be a Dictionary subclass for a lot of reasons.
Function Operator_Compare(rightSideOperand as Variant) as Double
The Operator_Compare() function overloads the "=" operator. It tests to see if one operand is equal to the other one. The developer gets to decide what constitutes "equal to."
This is illustrated in the following example, which was implemented in the Properties class. The Properties object is the operand on the left side of the expression, and the right side of the expression is the argument passed using the "readable as ReadableByLine" parameter.
One thing you should notice right away is that the right side operand isn't of the same class as the left side operand; that's okay because you get to decide how this works. I find this approach useful because I may want to compare a file with an instantiated Properties object to see if there are any differences between the two. It's an extra step if I have to open the file, instantiate a Properties object, and then compare it with the original. This approach lets me compare the values represented by two distinct but related objects.
In practice, you can return any numeric data type—an integer, a short, or a double, but the answer is evaluated much like the StrComp() function. In other words, the number 0 means that both operands are equal to each other. A number greater than 0 means the left-side operand is greater than the right-side operand, and a number less than 0 means the left operand is less than the right.
Function Operator_Compare(readable as ReadableByLine) as Integer // mode = lexicographic dim newReadable as ReadableString Return StrComp(me.getString, readable.getString, 1 ) Function
This example actually uses StrComp() to make the comparison between the two related objects. It compares the string provided by the Properties object with that provided by the ReadableByLine argument. The function returns the results of the StrComp() function.
Function Operator_Convert() as Variant
There is an Operator_Convert() function and an Operator_Convert subroutine, both of which work with the assignment operator, but in slightly different ways. Consider the following implementation:
Function Operator_Convert() as String Return Me.getString() End Function
This is how it is used:
Dim prop as Properties Dim s as String prop = New Properties("a=first" + chr(13) + "b=second") s = prop // s equals the string '"a=first" + chr(13) + "b=second"'
In this example, the prop object is assigned to a variable with a string data type. The Operator_Convert() function coerces the prop object into a string. This is functionally equivalent to called the prop.getString() method.
Sub Operator_Convert(rightSideOperand as Variant)
The subroutine version handles assignment as well. The difference is that whereas the function assigns the value of the object executing the method to some other variable, the subroutine assigns the value of some other variable to the object executing the method. The argument passed in the "readable as ReadableByLine" parameter is the object whose value is being assigned to the calling object.
Sub Operator_Convert(readable as ReadableByLine) Dim tmpReadable as ReadableByLine If Not (me.readableSource is Nil) Then Me.Clear tmpReadable = Me.readableSource Me.readableSource = Nil Try Me.parsePropertyFile(readable) Catch // If new file fails, restore old version me.readableSource = tmpReadable me.parsePropertyFile(me.readableSource) End Else // No pre-existing data; so read new data Me.parsePropertyFile(readable) End If End Function
In this example, I assign the value of a ReadableByLine object to that of a Properties object. This means that I can use the assignment operator ("=") to instantiate a new Properties object.
Dim prop1, prop2 as Properties Dim s1, s2 as String Dim r as ReadableString s1 = "a=first" + chr(13) + "b=second" s2 = "c=third" + chr(13) + "d=fourth" prop1 = New Properties(s1) r = New ReadableString(s2) prop2=r
In this example, prop1 is instantiated using the New operator, whereas prop2 is instantiated using simple assignment. In a sense, this functionality gives the Properties class something of the flavor of a data type.
Addition and Subtraction
You can also overload the "+" and "-" operators, enabling you to add objects to each other or subtract objects from each other. You have already seen an overloaded version of "+" that is used with strings. When used with strings, the "+" operator concatenates the strings. Because our Properties class deals with strings, you can create similar functionality for your objects. In this case, you can add two Properties objects together and this will concatenate the strings associated with each object and return a new object generated from the concatenated string.
Example 3.1. Function Operator_Add(rightSideOperand as Variant) as Variant
Function Operator_Add(readable as ReadableByLine) as Properties // Since parsePropertyFile does not reset //the data when called, this adds more values //to the Property object Dim s as String Dim newProp as Properties s = Self.getString + EndOfLine.UNIX + readable.getString newProp = New Properties(s) return newProp End Function
When you implement the Operator_AddRight() version, you do the same thing, but switch the order in which the strings are concatenated. In the following example, note that the string concatenation happens in the opposite order from the previous example.
Example 3.2. Operator_AddRight(leftSideOperand as Variant) as Variant
Function Operator_AddRight(readable as ReadableByLine) as Properties Dim prop as Properties Dim s as String s = readable.getString+ EndOfLine.UNIX + me.getString prop = New Properties(s) Return prop End Function
Operator_Subtract(rightSideOperand as Variant) as Variant, Operator_SubtractRight(leftSideOperand as Variant) as Variant
The subtraction operators work in a predictable way, just like the add operators, except that the values are being subtracted. Although I did not implement this method in the Properties class, I can imagine it working something like this: Any key/value pair that exists in the left-side operand is deleted from that object if it also exists in the right-side operand. If I wanted to delete a group of key/value pairs from a Properties object, I could use a Properties object that contained these key/value pairs as one of the operands.
Operator_And(rightSideOperand as Variant) as Boolean, Operator_AndRight(leftSideOperand as Variant) as Boolean, Operator_Or(rightSideOperand as Variant) as Boolean, Operator_OrRight(leftSideOperand as Variant) as Boolean
For the And operator to return True, both operands must evaluate to True as well. Here's a scenario where this might make sense: the Properties class (and many other classes, too) can be instantiated, but not populated by any values. If you tested to see if the operands were equal to Nil, you would be told that they are not Nil. However, it might be useful to be able to distinguish whether there are any values at all in the object. You can use the And operator to do this. Within the Operator_And method, test to see if each operand has a value. If both do, return True. If one or more does not, return False.
More Operator Overloading
There are several more operators that can be overloaded, but they do not have an application with the Properties class. They work much like the ones I have covered here, except that they overload negation, the Not operator, and the multiplication operators such as *, /, Mod, and so on. They are
Operator_Negate() as Variant Operator_Not() as Boolean Operator_Modulo(rightSideOperand as Variant) as Variant Operator_ModuloRight(leftSideOperand as Variant) as Variant Operator_Multiply(rightSideOperand as Variant) as Variant Operator_MultiplyRight(leftSideOperand as Variant) as Variant Operator_Power(rightSideOperand as Variant) as Variant Operator_PowerRight(leftSideOperand as Variant) as Variant Operator_Divide(rightSideOperand as Variant) as Variant Operator_DivideRight(leftSideOperand as Variant) as Variant Operator_IntegerDivide(rightSideOperand as Variant) as Variant Operator_IntegerDivideRight(leftSideOperand as Variant) as Variant
Finally, there is an additional way of extending a class that does not involve subclassing or interfaces. REALbasic allows you to implement methods in Modules and use those methods to extend or add functionality to other classes. Some classes, like the TextInputStream class I discussed earlier, can't really be effectively subclassed in REALbasic, and this is where using the Extends keyword can come in handy because it gives you an opportunity to add methods to TextInputStream without subclassing it.
I'll start with an example, which should make things clearer.
Unix uses a different epoch to measure time than REALbasic does. Instead of January 1, 1904 GMT, Unix measures time since January 1, 1970 GMT. The difference between these two values is 2,082,848,400 seconds. One useful addition to the Date object would be a method that converted REALbasic's measurement of time to the way that Unix measures time.
It's possible to subclass Date, but that only provides a partial solution because it doesn't help you with all the other instances of data objects that you encounter while programming. For example, the FolderItem class has Date properties for the file creation date and the date the file was last modified. A subclass won't help you there.
It is for this kind of problem in particular that Extends is made. Extends allows you to extend a class with a new method without having to subclass it. The method that will be used to extend the class must be implemented in a module, and the first parameter must be preceded by the Extends keyword, followed by a parameter of the class that is being extended.
The following example adds a method UnixEpoch that can be called as a member of the Date class. It returns the date as the number of seconds since January 1, 1970, rather than January 1, 1904.
Protected Function UnixEpoch(Extends d as Date) As Double Return d.TotalSeconds - 2082848400 End Function
In addition to being an object-oriented programming language, one of the most important distinguishing characteristics of REALbasic is that it is a cross-platform application development environment. You can compile applications for Windows, Macintosh, and Linux. In my experience, REALbasic is the easiest language to use for this kind of development, but it is not without its own idiosyncrasies and, despite their similarities, the three platforms vary in fundamental ways that a developer needs to be aware of. In the next chapter, I write about the cross-platform features of REALbasic and how the differences among the platforms will impact your development.