2009/11/27 Colin Putney cputney@wiresong.ca:
On 26-Nov-09, at 2:48 PM, Nicolas Cellier wrote:
The path to a cleaner/faster stream library is longer than just this little step. Beside testing, we'd have to refactor the hierarchy, insulate all instance variables, and delegate as much as possible as Igor suggested. We'd better continue on the cleaning path and not just add another FileStream subclass complexifying a bit more an unecessarily complex library.
I've been thinking about this too. For Filesystem, I've only implemented very basic stream functionality so far. But I do intend to develop its stream functionality further, and to go in a very different direction from the existing design. Some design elements:
- Using handles to decouple the streams from the storage they're operating
on. The same stream class should be able to read or write to collections, sockets, files etc.
- Separating ReadStream from WriteStream. I find code that both reads and
writes to a particular stream to be very rare in practice, and in cases where it does happen, reading and writing are separate activities and using separate streams wouldn't introduce problems. On the other hand, a lot of the complexity in the existing hierarchy stems from the mingling of read and write functionality.
- Simplified protocols. The existing stream classes have accumulated a lot
of cruft that should be implemented as objects use streams rather than being streams themselves. Examples include fileIn, fileOut, RefrenceStream etc.
- Composition rather than inheritance. As I go about implementing string
encoding, buffering, compression etc. I plan to enable the creation of stream pipelines to provide combinations of functionality. Instead of implementing BufferedUtf8DelfateFilestream, I want to create a sequence of streams like this:
WriteStream -> Utf8Encoder-> DeflateCompressor -> Buffer -> Handle
+100. Just yesterday i thought about same design principle: composition. I call it StreamAdaptor. It should carry a minimal set of methods, which providing a basic set of operations (read/write/seek etc) and also should support a pipelining in same way as you illustrated above:
Lets say, initially we created a stream which works with file: Stream -> FileAdaptor
then we want it to be buffered: stream adaptor: (stream adaptor beBuffered)
Stream -> BufferAdaptor -> FileAdaptor
then we want it to be compressed:
stream adaptor: (ZipAdaptor on: stream adaptor)
Stream -> DeflateCompressor -> BufferAdaptor -> FileAdaptor
and so on..
It is easy to see, that if we may want to create same structure for socket connection, all we need is to just use a socket adaptor in the chain, while rest don't requires any modifications.
- Grow the new streams parallel to the existing ones. Rather than trying to
maintain backwards compatibility, leave the old streams in place and continue to improve them while the new ones are being developed. Migration to the new streams can happen gradually. If the new streams don't attract any users, obviously I'm on the wrong track. :-)
So I've been watching your cleanup efforts with interest, particularly the buffering stuff. Keep it up!
Colin