New to version 5.5 is the concept of the Readable interface. Most people have never heard of this interface because it's neatly tucked away in class APIs that didn't have to change to support it.
What this interface provides is a neat abstraction layer for your applications. It gives you the way to write generic code that works using only this interface contract so that you can easily interchange various implementations -- it gives you power.
Let's take a real-life example to demonstrate what I mean. Let's say you're writing a serial application that talks to some device. The device sends you packets of data at random intervals and you need to be able to process the data.
Most people would just use the Serial control in REALbasic and talk directly to the device. Obviously, this approach works. But it's also very inflexible. Let's say that the device is a royal pain to get to, or you only have limited access to it (such as you may have with research equipment). Your application is stuck because you've been using just a Serial control, and without the device on the other end, you have no way to test.
If you had instead written your code such that all communications happen via objects that implement the Readable interface, you'd have a much better time of things. This is because you could easily change out the Serial control implementation with a BinaryStream implementation. Now your code can read the packets directly from a file (which you can easily modify without the device being present) and test all of your code off-line. When it comes time to actually use the device, hook in the Serial implementation, and you're good to go. Now let's say the university puts an ethernet interface on the device so you can access it via TCP -- no problem for you, just make a TCPSocket implementation and hook it into the rest of your logic!
Basically, the Readable interface lets you have interchangable parts. The main logic of your application doesn't have to change. It just gets data from a Readable class, and doesn't have to care which one. Now you can swap out one Readable class for another, and the program logic still works.
I find that using this design pattern makes life a lot easier. I don't like to sit down in my truck when writing code for the OBD-II application (code-named "Trucker") because it's hot, my laptop runs out of juice after a few hours, etc. So instead, I do all my work from the comfort of my couch using a BinaryStream reader, but I can go do real-world testing in the truck when I need to by hooking up to the Serial reader instead. This makes testing a lot easier!
So how would you design an application that makes use of the Readable interface? The way I do it is by wrapping or subclassing some intrinsic classes and then use a controller object. I make use of the Observer pattern (which I will describe some other day), which is where the Notify function comes from. Suffice it to say that the wrapper/subclasses are responsible for initiating the Notify method call.
[rbcode]
Class DataHandler Implements Observer
Dim mReceiver as Readable
Sub Constructor( reader as Readable )
mReceiver = reader
End Sub
Function Notify( action as String ) as Boolean
dim data as MemoryBlock
if action = "Data Available" then
// For the sake of demonstration,
// my data is only 25 bytes long
data = mReader.Read( 25 )
return ProcessData( data )
end if
End Function
End Class
[/rbcode]
Keep in mind that I've stripped a large chunk out of this example to keep things simple. In my real-world projects, I tend to use one more layer of abstraction over the Readable layer (also using interfaces). This is so that I can do things like Peek data instead of reading it in from the buffer. However, you should be able to see from this very simple example that it's trivial to hook the DataHandler class up with any number of objects. I could create a DataHandler that works with a BinaryStream, or a TCPSocket, or a Serial control, etc. I just have to pass an instance of the class in to the DataHandler's constructor and it all just works. This is the true power of using interfaces in general -- they allow for looser coupling between parts of your application.
When I was writing my FTP server I started out just reading the file into RAM and then sending it through the data connection. Then after I finished up other stuff, I came back and made it use streams. I figured, let's be more flexible and use the Readable interface (and the Writable for incoming files). The only problem I ran into was that a Close method isn't specified in the interfaces. I ended up checking if it isa BinaryStream and casting it into that then closing it.
-- SirG3
Yeah, I wondered if Close would make sense when we were discussing the design of the interface. Unfortunately, it doesn't -- things like StdIn don't have the concept of a closed state. But I don't think it would have been so bad to add Close to the interface. The unfortunate thing is that it's tough to change interfaces once they're exposed since it breaks everyone's code.
I don't see how *adding* to an interface breaks code...
As for the close function, I think it should be included. It can simply do nothing in contexts where it doesn't make sense.
-- SirG3
Adding to the interface means that everyone who is currently implementing that interface but does not have the added method with the same signature now has a project that will not compile until they add the new method.
Readable is cool, but there's something not quite right about the way BinaryStream is factored. I guess the problem is that it isn't factored at all. Ideally there would be a Stream base class that would have the same API as BinaryStream. It would also have two "virtual" methods (that didn't do anything) called Read and Write. Read would read an arbitrary number of bytes and Write would write an arbitrary number of bytes. All of the special ReadXXX and WriteXXX methods would be written to use Read and Write. To implement a concreate Stream, BinaryStream for example, you would subclass Stream and then implement Read and Write. You could also have BufferStream for in-memory streams, NetworkStream for streams across networks, and probably a bunch others I can't think of right now.
I totally agree Will. A Stream interface would rock!
Btw, you DO know about the new BinaryStream functionality in 2005, right? How you can have it backed by a MemoryBlock, a String or a file?
Uh, no I don't. Do I? Maybe I do and I'm forgetting. I'm going to have to go back to the release notes. Thanks for pointing that out.