Как я могу сходить как с TFileStream, так и с TMemoryStream?

У меня есть класс, который наследует от 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;

5 ответов

Решение

Чтобы ответить на вопрос в самом названии вопроса: Вы не можете.:)

Но если мы сделаем шаг назад и посмотрим на проблему, которую вы пытались решить:

У меня есть класс, который наследует от TFileStream и класс, который наследует от TMemoryStream. Оба реализуют одинаковые функции для чтения данных.

Я думаю, что вы неправильно сформулировали свою проблему, и правильное ее повторение указывает на нужный вам ответ.:)

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

Поток - это просто набор байтов. Любая структура в этих байтах определяется тем, как вы читаете / записываете поток. т.е. в этом случае это "как" воплощено в ваших функциях. Тот факт, что используемые классы конкретных потоков - это TFileStream и TMemoryStream, принципиально не является частью проблемы. Решите проблему для 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. Для этих функций не должно иметь значения, какой конкретный класс потока задействован.

Если ваша проблема включает в себя необходимость передавать ссылки на поток в различные функции и т. Д., То вместо этого вы передаете ссылку на класс читателя. Считыватель обязательно несет с собой ссылку на соответствующий поток, но код, который ранее считался необходимым для вызова функций в специализированных классах потока, вместо этого просто использует функцию считывателя. Если этот код также нуждается в доступе к самому потоку, то читатель может раскрыть его при необходимости.

Затем в будущем, если вам когда-нибудь понадобится прочитать такие данные из других потоковых классов (например, TBLOBStream, если вы обнаружите, что извлекаете данные из базы данных), ваш TStuffReader (или как вы его называете) может сразу же приступить к работа для вас без дальнейшей работы с вашей стороны.

Помощник класса безальтернативный

Может показаться, что помощники класса предоставляют механизм для аппроксимации "множественного наследования", но его следует избегать. Они никогда не предназначались для использования в коде приложения.

Присутствие помощников классов в VCL точно так же, как и следовало ожидать, поскольку они предназначены для использования в каркасах и библиотеках, а VCL является библиотекой каркасов, поэтому его использование полностью соответствует этому использованию.

Но это не подтверждение того, что они подходят для использования в коде приложения, и документация продолжает поддерживать этот пункт.

Помощники класса и записи предоставляют способ расширения типа, но их не следует рассматривать как инструмент проектирования, который будет использоваться при разработке нового кода. Для нового кода вы всегда должны полагаться на обычное наследование классов и реализацию интерфейса.

В документации также достаточно четко изложены ограничения, которые применяются к помощникам класса, но не ясно объясняется, почему они могут привести к проблемам, и, возможно, поэтому некоторые люди все еще настаивают на том, что они пригодны для использования.

Я освещал эти проблемы в блоге вскоре после того, как они были представлены (и те же проблемы все еще применяются сегодня). На самом деле, это такая проблема, что я написал ряд постов по этой теме.

Кажется, есть нежелание отпустить мысль о том, что, пока вы осторожны со своими помощниками, вы не столкнетесь с проблемами, то есть игнорируете тот факт, что независимо от того, насколько вы осторожны, ваше использование помощников может быть сломанным чужим столь же осторожным использованием, если Вы заканчиваете тем, что поделились кодом с ними.

И нет более общего кода в Delphi, чем сам VCL.

Использование (дополнительных) помощников в VCL повышает вероятность возникновения проблем с вашими собственными помощниками, а не меньше. Даже если ваш код отлично работает с вашими помощниками в одной версии VCL, следующая версия может что-то сломать.

Это далеко не рекомендация для более широкого использования помощников, их распространение в VCL является лишь одной очень веской причиной, по которой вам следует избегать их.

Прежде всего: множественное наследование невозможно в 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 как у него были эти дополнительные методы на протяжении веков.

Вы можете иметь отдельный класс читателя, работающий с (абстрактным) потоком. Посмотрите, например, на TBinaryReader в Classes для вдохновения.

Вы можете поместить свои общие методы в 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;

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;

Эти обходные пути - лишь некоторые идеи, которые помогут вам приблизиться к тому, что вы хотите. Измените их, чтобы они соответствовали вашим потребностям.

Другие вопросы по тегам