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派生クラスについて解決できます。

ストリーム クラスは、バイト内の特定の構造ではなく、特定の場所 (メモリ、ファイル、文字列など) との間でバイトを読み書きする方法に基づいて特化する必要があります。

構造の知識を複製しなければならない特殊なストリーム クラスを作成する代わりに、本当に必要なのは、関係するデータの構造の知識をカプセル化し、それを任意のストリームに適用できるクラスです。

リーダー クラス

これに対する 1 つのアプローチ (最善の方法は?) は、必要な動作をカプセル化するクラスを実装することです。たとえば、「リーダー」クラスで。

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 の 1 つのバージョンでコードが独自のヘルパーと完全に正常に動作したとしても、次のバージョンでは問題が発生する可能性があります。

ヘルパーの使用を増やすことを推奨するものではありませんが、VCL でヘルパーが急増していることは、ヘルパーを避けるべき非常に正当な理由の 1 つにすぎません。

于 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 に答える
0

共通メソッドを に配置し、 、およびメソッドを各子孫クラスに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 に答える