Методы класса в записи против класса в Delphi 2010

Я только начал играть с новым модулем в Delphi 2010 IOUtils.pas, и я обнаружил, что они помещают все методы внутри Records(TFile, TPath, TDirectory) в функции класса и процедуру.

Есть ли преимущества в том, что внутри записей вместо классов? В обоих случаях нет необходимости в каких-либо переменных или экземплярах, но я не уверен, есть ли какие-либо реальные преимущества в отношении потребления памяти или улучшения производительности.

Ответы

Ответ 1

Методы класса в записях используются для группировки различных методов в общее пространство имен. Таким образом, вы можете иметь похожие именованные методы для разных целей. Для примера в IOUtils.pas смотрите функцию Exists, доступную в TFile и в TDirectory. Более старый подход заключался в том, чтобы иметь различные имена функций для FileExists и DirectoryExists (которые фактически выполняются реализацией).

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

Ответ 2

Я бы сказал, что первый вопрос, который нужно задать, - "Зачем ставить такие функции внутри записи или класса?" в первую очередь.

Использование методов записи, как в IOUtils, во многих случаях во многом является произвольным в Delphi и частично отражает фетиш со следующими соглашениями на других языках, которые не имеют прямого отношения к Delphi.

В некоторых языках (Java и .NET?) функции "первого класса" не поддерживаются. То есть процедуры и функции не могут существовать независимо от любого класса контейнера. Следовательно, на этих языках, если у вас есть функции и процедуры, они должны содержаться в классе. Если они не работают с данными-членами, то, очевидно, они объявляются как методы класса, чтобы избежать необходимости создавать экземпляр, просто чтобы вызвать то, что на самом деле является функцией classLESS.

то есть.

TDirectory.Exists(s) и TFile.Exists(s) являются синтаксическими альтернативами DirectoryExists (s) и FileExists (ы).

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

Без записи/класса контейнера вы должны знать, что существует функция, называемая "DirectoryExists()". С записью контейнера вам нужно только знать, что существует тип TDirectory, а среда IDE (через предложения по завершению кода) может представить все "методы", доступные для операций TDirectory.

Однако в Delphi существует альтернатива, обеспечиваемая поддержкой функций первого класса. Вместо использования произвольных и искусственных "контейнеров" вы (или, скорее, авторы IOUtils) могли вместо этого использовать существующий контейнер - блок Delphi:

  unit IOUtils.Directory;

  interface

  function Exists(s): Boolean;

и

  unit IOUtils.File;

  interface

  function Exists(s): Boolean;

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

Недостатком этого подхода (и он является большим) является то, что, когда единица использует как IOUtils.File, так и IOUtils.Directory, он должен квалифицировать один или другое имя функции, чтобы избежать путаницы, но эта квалификация может быть опущена, поэтому она настолько опасна:

  uses
    IOUtils.File,
    IOUtils.Directory;
  ..
  begin
    if Exists(s) then  // << tests existence of a directory
    ..
    if IOUtils.File.Exists(s) then  // << ensure we test for a file
    ..
    if IOUtils.Directory.Exists(s) then  // << ensure we test for a directory
  end

Это также может быть преимуществом, конечно, если ваш блок содержит код, который ТОЛЬКО работает с каталогами, он будет использовать только IOUtils.Directory и не должен квалифицироваться и повторять этот факт где угодно, кроме как в uses.

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

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

По этим причинам выбор дизайна в IOUtils по крайней мере является понятным и, возможно, хорошим, tho ymmv.

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

Что касается того, почему запись, а не класс...

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

Ответ 3

Статические методы класса - это просто синтаксический сахар - вы всегда можете реализовать их как обычные процедуры/функции.

Если метод класса реализован внутри записи, накладные расходы меньше. Любое объявление класса требует дополнительной структуры для информации о классе в памяти. Записи не имеют структуры.

Ответ 4

IMO, для использования записей по классам со статическими методами потребуется очень хорошая причина. Как для TValue, когда скорость имеет значение. Например, просто получается, что TDirectory.CreateDirectory() просто не работает для UNC-путей, потому что TPath.DriveExists() не работает для UNC-пути. (D2010) Если бы это был класс, я бы просто написал вспомогательный класс, реализующий мой собственный TDirectory.CreateDirectoryEx(). Поскольку его запись я не могу и либо скопирую код из IOUtils, либо напишу свой собственный. В любом случае я получаю избыточность кода. Так что с точки зрения выдающегося кода выбор записей - плохая идея. (На самом деле я до сих пор удивляюсь, почему команда VCL решила не рассматривать расширяемость. Я имею в виду сравнение VCL с DevEx QuantumGrid в этом аспекте. Между ними есть свет)

Хорошо, я только что узнал, что вы можете написать хелпер для записи:

TDirectoryHelper = record helper for TDirectory

end;

Но это не очень помогает, поскольку вы не можете получить доступ к какому-либо приватному статическому методу из TDirectory