4

我有一个从 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);

这当然意味着我不能使用我的自定义函数。处理这个问题的最佳方法是什么?理想情况下,我希望能够拥有一个在 FileStream 或 MemoryStream 上工作的 Tstream 兼容类,这样我就可以做这样的事情,并且流是 FileStream 还是 MemoryStream 无关紧要:

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

end;
4

5 回答 5

7

回答实际问题标题中的问题: 你不能。:)

但是,如果我们退后一步,看看你试图解决的问题:

我有一个从 TFileStream 继承的类和一个从 TMemoryStream 继承的类。两者都实现了与读取数据完全相同的功能

我认为您错误地陈述了您的问题并正确地重新陈述它指向您需要的答案。:)

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

流只是一堆字节。这些字节中的任何结构都取决于您如何读/写流。即在这种情况下,“如何”体现在您的功能中。所涉及的具体流类是TFileStreamTMemoryStream的事实从根本上不是问题的一部分。为TStream解决问题,然后为所有TStream派生类解决它,包括您现在正在处理的那些。

流类应该根据它们需要如何从特定位置(内存、文件、字符串等)读取/写入字节而不是这些字节中的任何特定结构来专门化。

与其创建必须复制结构知识的专用流类,您真正需要的是一个封装所涉及数据结构知识并能够将其应用于任何流的类。

读者班

解决这个问题的一种方法(最好的?)是实现一个封装您需要的行为的类。例如在“读者”类中。

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派生类读取(和/或写入)结构化数据。对于这些函数来说,涉及到什么特定的流类应该是无关紧要的。

如果您的问题包括需要将流的引用传递给各种函数等,那么您可以将引用传递给阅读器类。reader 必须携带对所涉及的流的引用,但是以前您认为需要调用专用流类上的函数的代码只是使用 reader 函数。如果该代码还需要访问流本身,则阅读器可以在必要时公开它。

然后在将来,如果您发现自己需要从其他流类中读取此类数据(例如,如果您发现自己从数据库中检索数据,则为 TBLOBStream),您的TStuffReader 或您选择的任何名称)可以直接介入并执行无需您进行任何进一步的工作即可为您完成这项工作。

类助手非替代

类助手似乎提供了一种近似“多重继承”的机制,但应该避免。它们从未打算用于应用程序代码。

VCL 中类助手的存在与您所期望的完全一样,因为它们旨在用于框架和库中,并且 VCL 是一个框架库,因此它的使用与该用法完全一致。

但这并不是对它们适用于应用程序代码的认可,并且文档继续强制执行这一点。

类和记录助手提供了一种扩展类型的方法,但不应将它们视为开发新代码时使用的设计工具。对于新代码,您应该始终依赖正常的类继承和接口实现。

文档也很清楚适用于类助手的限制,但没有清楚地解释为什么这些会导致问题,这也许就是为什么有些人仍然坚持认为它们适合使用的原因。

在引入这些问题后不久,我在一篇博文中介绍了这些问题(同样的问题今天仍然适用)。事实上,正是这样一个问题,我写了很多关于这个主题的帖子

似乎不愿意放弃这样一种观念,即只要你小心你的助手,你就不会遇到问题,那就是忽略一个事实,即无论你多么小心,对助手的使用都可以如果您最终与他们共享代码,则会被其他人同样谨慎的使用所破坏。

Delphi 中没有比 VCL 本身更多的共享代码。

在 VCL 中使用(额外的)助手使您自己的助手遇到麻烦的可能性更大,而不是更少。即使您的代码在 VCL 的一个版本中与您自己的助手完美配合,下一个版本也可能会破坏事情。

远非建议更多地使用助手,它们在 VCL 中的扩散只是你应该避免使用它们的一个很好的理由。

于 2016-08-31T04:28:36.763 回答
6

首先:在 Delphi 中不可能进行多继承。

您说自定义流类的方法对它们都实现相同?您可以以流阅读器类的形式使用装饰器模式。

另一方面,您可以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像几个世纪以来使用这些附加方法一样使用它。

于 2016-08-30T14:02:14.123 回答
4

您可以在(抽象)流上运行一个单独的阅读器类。看看例如TBinaryReaderinClasses以获得灵感。

于 2016-08-30T13:55:51.560 回答
0

Delphi 不支持多重继承,在这种情况下没有意义。

可能的解决方案

您可以做的是编写一个实现TStream和接受内部流的类,该流可能是TFileStreamor 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;

这样你就有了你需要的确切课程;支持流默认方法和您的自定义方法。

可选的

如果您的自定义必须有两个不同的类TFileStreamTMemoryStream那么您可以执行以下操作:

class TMyFileStream : TMyStream
public
    constructor Create(const Path:String); reintroduce;
end

constructor TMyFileStream.Create(const Path:String);
begin
    inherited Create(TFileStream.Create(Path));
end;

这些变通方法只是帮助您接近所需内容的一些想法。修改它们以使它们满足您的需求。

于 2016-08-30T13:57:20.497 回答
0

您可以将常用方法放在 an中,并在每个后代类中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;
于 2016-08-30T14:10:55.427 回答