Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I descend from both TFileStream and TMemoryStream?

I have a class that inherits from TFileStream and a class that inherits from TMemoryStream. Both implement exactly the same functions to do with reading data eg:

TCustomFileStream = class (TFileStream)
  function ReadByte: byte;
  function ReadWord: word;
  function ReadWordBE: word;
  function ReadDWord: longword;
  function ReadDWordBE: longword;
  function ReadString(Length: integer): string;
  function ReadBlockName: string;
  etc

When I want to write a function that could take either type of stream as a parameter I have to use TStream:

function DoStuff(SourceStream: TStream);

This of course means that I cant use my custom functions. Whats the best way of dealing with this? Ideally I'd like to be able to have a Tstream compatible class that works on either a FileStream or MemoryStream so I can do something like this and it wont matter if the stream is a FileStream or MemoryStream:

function DoStuff(SourceStream: TMyCustomStream);
begin
    data := SourceStream.ReadDWord;
    otherData := SourceStream.Read(Buffer, 20);

end;
like image 370
Bennyboy Avatar asked Dec 02 '22 14:12

Bennyboy


2 Answers

To answer the question in the actual question title: You can't. :)

But if we take a step back and look at the problem that you were trying solve:

I have a class that inherits from TFileStream and a class that inherits from TMemoryStream. Both implement exactly the same functions to do with reading data

I think you have mis-stated your problem and re-stating it correctly points to the answer you need. :)

I have some structured data that I need to read from different sources (different stream classes).

A stream is just a bunch of bytes. Any structure in those bytes is determined by how you read/write the stream. i.e. in this case, that "how" is embodied in your functions. The fact that the concrete stream classes involved are a TFileStream and TMemoryStream is not fundamentally a part of the problem. Solve the problem for TStream and you solve it for all TStream derived classes, including the ones you are dealing with right now.

Stream classes should be specialised based on how they need to read/write bytes to and from specific locations (memory, file, strings etc) not any particular structure in those bytes.

Instead of creating specialised stream classes which have to duplicate the knowledge of the structure, what you really need is a class that encapsulates the knowledge of the structure of the data involved and is able to apply it to any stream.

A Reader Class

One approach to this (the best ?) is to implement a class that encapsulates the behaviour you require. For example in a "reader" class.

TStuffReader = class
private
  fStream: TStream;
public
  constructor Create(aStream: TStream);
  function ReadByte: byte;
  function ReadWord: word;
  function ReadWordBE: word;
  function ReadDWord: longword;
  function ReadDWordBE: longword;
  function ReadString(Length: integer): string;
  function ReadBlockName: string;
end;

This class might also provide the functions for writing to a stream as well (in which case you might call it a TStuffFiler, for example, since it would not be just a reader) or you might have a separate class for writing called TStuffWriter (for example).

However you choose to implement it, this reader class will be able to read (and/or write) that structured data from/to any TStream derived class. It should not matter to these functions what specific class of stream is involved.

If your problem includes a need to pass references to a stream around to various functions etc then instead you pass around a reference to the reader class. The reader necessarily carries with it a reference to the stream involved, but the code that previously you thought needed to call the functions on the specialised stream classes instead simply uses the reader function. If that code also needs access to the stream itself then the reader can expose it if necessary.

Then in the future if you ever found yourself needing to read such data from other stream classes (for example a TBLOBStream if you find yourself retrieving data from a database) your TStuffReader (or whatever you choose to call it) can step right in and do the job for you without any further work on your part.

The Class Helper Non-Alternative

Class helpers might appear to provide a mechanism to approximate "multiple inheritance" but should be avoided. They were never intended for use in application code.

The presence of class helpers in the VCL is exactly as you would expect since they are intended for use in frameworks and libraries and the VCL is a framework library so it's use there is entirely consistent with that usage.

But this is not an endorsement that they are suitable for use in application code and the documentation continues to enforce this point.

Class and record helpers provide a way to extend a type, but they should not be viewed as a design tool to be used when developing new code. For new code you should always rely on normal class inheritance and interface implementations.

The documentation is also quite clear on the limitations that apply to class helpers but do not clearly explain why these can lead to problems, which is perhaps why some people still insist that they are suitable for use.

I covered these problems in a blog post not long after they were introduced (and the same problems still apply today). In fact, it is such an issue that I wrote a number of posts covering the topic.

There seems to be a reluctance to let go of the notion that as long as you are careful with your helpers then you won't run into problems, which is to ignore the fact that no matter how careful you are, your use of helpers can be broken by somebody else's equally careful use, if you end up sharing code with them.

And there is no more shared code in Delphi than the VCL itself.

The use of (additional) helpers in the VCL makes the prospect of running into trouble with your own helpers more likely, not less. Even if your code works perfectly well with your own helpers in one version of the VCL, the next version could break things.

Far from being a recommendation for more use of helpers, their proliferation in the VCL is just one very good reason that you should avoid them.

like image 153
Deltics Avatar answered Dec 05 '22 03:12

Deltics


First of all: Multi-inheritance is not possible in Delphi.

You say the methods of your custom stream classes are implemented the same for both of them? You could either use the decorator pattern in form of a stream reader class.

On the other hand, you could extend TStream by writing a class helper for it:

TCustomStreamHelper = class helper for TStream
  function ReadByte: byte;
  function ReadWord: word;
  function ReadWordBE: word;
  function ReadDWord: longword;
  function ReadDWordBE: longword;
  function ReadString(Length: integer): string;
  function ReadBlockName: string;
  // etc.
end;

Thus, in every unit where TCustomStreamHelper is known by the compiler (because you added its unit to the uses clause), you can use TStream like it had those additional methods for centuries.

like image 45
René Hoffmann Avatar answered Dec 05 '22 04:12

René Hoffmann