There are times when you may want to add a little functionality to an existing REALbasic class, but making a subclass simply doesn't make sense. One question that comes up over and over again are how to get REALbasic to return a new instance of a subclass from one of its intrinsic methods. For example, how do I get GetFolderItem to return MySpiffyFolderItemClass instead of FolderItem, or how do I get FolderItem.OpenAsBinaryFile to return a MySpiffyBinaryStream?
The way I like to extend internal classes when I run into these situations is by using the extends keyword. This keyword was added in 5.0 (I believe) as one of the additions to the new compiler, and it's a great way for you to extend internal functionality in some circumstances.
Here's the set of rules I go by when trying to determine when I want to use extends or not:
- Am I only adding methods and not properties or other persistant storage?
- Do I want this functionality to be available to all instances of the class which I am extending?
If I answer yes to those questions, then I use the extends keyword. Let's extend the BinaryStream class to have a WriteCodedString and ReadCodedString method pair. These methods are going to write out a string of arbitrary length, and keep track of the size and encoding of the string.
First, you make a new module. This is because all class extensions must be inside of modules and have global scope. The name of the module doesn't matter, so I usually name it with the class I am extending, followed by Extensions. Then we'll add two new public methods to the module.
Module BinaryStreamExtensions
Sub WriteCodedString( extends b as BinaryStream, theStr as String )
Function ReadCodedString( extends b as BinaryStream ) as String
End Module
Notice that the first parameter to both methods is extends b as BinaryStream -- this tells the REALbasic compiler to interpret these methods as being on the actual BinaryStream class. Simply put, you've just added two new methods to the BinaryStream class, so you can say:
[rbcode]
dim bs as BinaryStream = f.OpenAsBinaryFile( false )
MsgBox bs.ReadCodedString
[/rbcode]
and it will call your module method. Neat, eh?
So let's take a look at the code for the actual methods. When writing out a "coded" string, we will write the length of the string out as a 4 byte long, followed by 4 bytes of encoding information, followed by the actual string.
[rbcode]
Sub BinaryStreamExtensions.WriteCodedString( extends b as BinaryStream, theStr as String )
// Write out the string's actual length
b.WriteLong( LenB( theStr ) )
// Now write out the string's encoding as an integer
b.WriteLong( theStr.Encoding.Code )
// Finally, write out the string itself
b.Write( theStr )
End Sub
[/rbcode]
There's a few things to note about the code. First, you'll notice that I don't check to see if b is nil. That's because it will never be nil -- the compiler inserts a nil object check, which will throw a NilObjectException if b is ever nil. The other thing is that the language reference says TextEncoding.Code is Mac-only. It's smoking something, that function works on all platforms.
Now, let's do the reverse operation. Let's read one of these coded strings back in. The code will be quite similar to the previous function.
[rbcode]
Function BinaryStreamExtensions.ReadCodedString( extends b as BinaryStream ) as String
// Read the length
dim length as Integer = b.ReadLong
// Check for sanity
if length < 0 then return ""
// Read the encoding as an Integer
dim enc as Integer = b.ReadLong
// Now read the string
dim ret as String = b.Read( length )
// Then set the encoding of the string and return it
return DefineEncoding( ret, Encodings.GetFromCode( enc ) )
End Function
[/rbcode]
As you can see, the code is pretty straightforward. We read the information in and return a string with the proper encoding. You'll note that we define the encoding instead of converting it -- that's because we only want to tell REALbasic what the encoding is, not change from one encoding to another.
Hopefully this simple example helped you with learning how and why you should be using the extends keyword.>
I really wish you could extend the system class / instance / whatever it happens to be. I would have redone a bit of the Carbon Declares Library to use extends and have a more RB feel to it if I could have extended the system class :-/.
-- SirG3
You can't extend the System class because it's not a class, it's a Module. And there's no way to extend modules in REALbasic. But I can see why it would be a nice thing to do -- you could extend Bitwise to add Rotate functions for example.
Worth a feature request.
Another thing to keep in mind is that when extending a class, the methods are not virtual, and therefore cannot be overridden further down the class hiearchy.
Still very useful, however :)