我有一个继承自TFileStream的类和一个继承于TMemoryStream的类。两者在读取数据时实现完全相同的功能,例如:
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
当我想写一个可以将任何类型的流作为参数的函数时,我必须使用TStream:
function DoStuff(SourceStream: TStream);
这当然意味着我不能使用我的自定义函数。处理这个问题的最佳方法是什么?理想情况下,我希望能够有一个Tstream兼容的类,它可以在FileStream或MemoryStream上工作,这样我就可以做这样的事情,不管流是FileStream还是MemoryStream:
function DoStuff(SourceStream: TMyCustomStream);
begin
data := SourceStream.ReadDWord;
otherData := SourceStream.Read(Buffer, 20);
end;
要回答实际问题标题中的问题:您不能。:)
但如果我们后退一步,看看你试图解决的问题:
我有一个从TFileStream继承的类和一个继承自TMemoryStream。两者实现完全相同的功能与读取数据有关
我认为你错误地陈述了你的问题,重新陈述正确地指向了你需要的答案。:)
I have some structured data that I need to read from different sources (different stream classes).
流只是一堆字节。这些字节中的任何结构都由读取/写入流的方式来决定。也就是说,在这种情况下,"如何"体现在你的功能中。所涉及的具体流类是TFileStream和TMemoryStreamStream的问题,并解决所有TS流派生类的问题,包括您现在正在处理的类。
流类应该根据它们需要如何向特定位置(内存、文件、字符串等)读取/写入字节而不是这些字节中的任何特定结构来进行专门化。
您真正需要的不是创建必须复制结构知识的专门流类,而是一个封装相关数据结构知识并能够将其应用于任何流的类。
读者课堂
实现这一点的一种方法(最好的?)是实现一个封装所需行为的类。例如在"阅读器"类中。
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;
该类还可能提供将写入流的函数(例如,在这种情况下,您可以将其称为TStuffFiler,因为它不仅仅是一个读取器),或者您可能有一个单独的编写类,名为TStuffWriter(例如)。
无论您选择如何实现它,该读取器类都能够从任何TStream派生类读取(和/或写入)该结构化数据。对于这些函数来说,所涉及的流的特定类别应该无关紧要。
如果您的问题包括需要将对流的引用传递到各种函数等,那么您可以将引用传递到阅读器类。阅读器必须附带对所涉及流的引用,但以前您认为需要调用专门流类上的函数的代码只使用阅读器函数。如果该代码还需要访问流本身,则读取器可以在必要时公开它。
然后,在未来,如果您发现自己需要从其他流类中读取此类数据(例如,如果您从数据库中检索数据,则为TBLOBStream),您的TStuffReader(或您选择的任何名称)可以直接介入并为您完成这项工作,而无需您做任何进一步的工作。
类帮助程序非替代
类帮助程序似乎提供了一种近似"多重继承"的机制,但应该避免。它们从未打算在应用程序代码中使用。
类助手在VCL中的存在正是你所期望的,因为它们是用于框架和库的,而VCL是一个框架库,所以它在那里的使用与该用法完全一致。
但这不是对它们适合在应用程序代码中使用的认可,文档继续强调这一点。
类和记录帮助程序提供了一种扩展类型的方法,但它们不应被视为开发新产品时使用的设计工具密码对于新代码,您应该始终依赖于普通类继承以及接口实现。
文档也非常清楚适用于类助手的限制,但没有清楚地解释为什么这些会导致问题,这可能就是为什么有些人仍然坚持认为它们适合使用的原因。
在这些问题被介绍后不久,我在一篇博客文章中谈到了这些问题(同样的问题今天仍然适用)。事实上,正是这个问题,我写了很多关于这个主题的帖子。
似乎有人不愿意放弃这样一种观念,即只要你小心对待你的助手,你就不会遇到问题,这就是忽略了一个事实,即无论你多么小心,如果你最终与他们共享代码,你对助手的使用都会被其他人同样小心的使用所破坏。
Delphi中没有比VCL本身更多的共享代码了。
在VCL中使用(额外的)助手会使您自己的助手遇到麻烦的可能性更大,而不是更少。即使您的代码在一个版本的VCL中与您自己的助手配合得非常好,下一个版本也可能会造成破坏。
与其建议更多地使用助手,它们在VCL中的扩散只是你应该避免它们的一个很好的理由。
首先:在Delphi中不可能实现多继承。
你说你的自定义流类的方法对它们都是相同的?您可以使用流读取器类形式的decorator模式。
另一方面,您可以通过为TStream
编写一个类助手来扩展它:
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;
因此,在编译器已知TCustomStreamHelper
的每个单元中(因为您将其单元添加到了uses
子句中),您可以使用TStream
,就像几个世纪以来使用这些附加方法一样。
您可以在(抽象)流上有一个单独的读取器类。例如,看看Classes
中的TBinaryReader
以获得灵感。
Delphi不支持多重继承,在这种情况下也没有意义。
可能的解决方案
您所能做的是编写一个实现TStream
并接受内部流(可能是TFileStream
或TMemoryStream
)的类。类似这样的东西:
class TMyStream = class(TStream)
private
InternalStream: TStream;
public
constructor Create(InternalStream:TStream);
/// implement all TStream abstract read, write, and seek methods and call InternalStream methods inside them.
/// implement your custom methods here
end;
constructor TMyStream.Create(InternalStream:TStream)
begin
Self.InternalStream=InternalStream;
end;
通过这种方式,您可以获得所需的确切类;同时支持流默认方法和自定义方法。
可选
如果您的自定义TFileStream
和TMemoryStream
必须有两个不同的类,那么您可以执行以下操作:
class TMyFileStream : TMyStream
public
constructor Create(const Path:String); reintroduce;
end
constructor TMyFileStream.Create(const Path:String);
begin
inherited Create(TFileStream.Create(Path));
end;
这些变通办法只是一些帮助你接近你想要的东西的想法。修改它们以满足您的需求。
您可以将常用方法放在interface
中,并在每个子类中实现QueryInterface
、_AddRef
和_Release
方法。
请参阅Delphi接口,无引用计数。
type
IStreamInterface = interface
function ReadByte: byte;
function ReadWord: word;
function ReadWordBE: word;
function ReadDWord: longword;
function ReadDWordBE: longword;
function ReadString(Length: integer): string;
function ReadBlockName: string;
end;
TCustomFileStream = class (TFileStream, IStreamInterface)
function ReadByte: byte;
function ReadWord: word;
function ReadWordBE: word;
function ReadDWord: longword;
function ReadDWordBE: longword;
function ReadString(Length: integer): string;
function ReadBlockName: string;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
TCustomMemoryStream = class (TMemoryStream, IStreamInterface)
function ReadByte: byte;
function ReadWord: word;
function ReadWordBE: word;
function ReadDWord: longword;
function ReadDWordBE: longword;
function ReadString(Length: integer): string;
function ReadBlockName: string;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
并为过程使用类型为IStreamInterface
的自变量:
procedure DoStuff(SourceStream: IStreamInterface);
var
data: Word;
begin
data := SourceStream.ReadDWord;
end;