Почему используются процедуры для создания объектов, предпочитающих функции?
Это похоже на этот вопрос. Я спросил: "Почему?" на самый популярный ответ, но я не знаю, что кто-нибудь снова посмотрит на него. По крайней мере, не в любой момент.
Во всяком случае, мой вопрос касается наилучшей практики делегирования ответственности за создание объектов для функций или процедур, не вызывая утечки памяти. Кажется, что это:
procedure FillObject(MyObject: TMyObject; SomeParam: Integer);
begin
//Database operations to fill object
end;
procedure CallUsingProcedure();
var
MyObject: TMyObject;
begin
MyObject = TMyObject.Create();
try
FillObject(MyObject, 1);
//use object
finally
MyObject.Free();
end;
end;
является предпочтительным по сравнению с этим:
function CreateMyObject(DBID: Integer): TMyObject;
begin
Result := TMyObject.Create();
try
//Database operations to fill object
except on E: Exception do
begin
Result.Free();
raise;
end;
end;
end;
procedure CallUsingFunction();
var
MyObject: TMyObject;
begin
MyObject = CreateMyObject(1);
try
//use object
finally
MyObject.Free();
end;
end;
Почему?
Я относительно новичок в Delphi, ранее работавший больше всего с Java и PHP, а также с С++, хотя и в меньшей степени. Интуитивно я склоняюсь к функциональному методу, потому что:
- Он инкапсулирует код создания объекта в функцию, а не создает объект отдельно, когда я хочу использовать эту процедуру.
- Мне не нравятся методы, которые изменяют их параметры. Он часто оставил недокументированным и может затруднить отслеживание ошибок.
- Смутно, но, по общему признанию, это просто "плохо пахнет" для меня.
Я не говорю, что я прав. Я просто хочу понять, почему сообщество выбирает этот метод, и если есть веские причины для меня изменить.
Edit:
Ссылки на @E-Rock в комментариях для меня (Eric G). Я изменил свое отображаемое имя.
Ответы
Ответ 1
Создание экземпляра объекта и передача его в другую процедуру дает понять, какой код отвечает за освобождение экземпляра.
В первом случае (с использованием процедуры для его заполнения):
MyObj := TMyObject.Create;
try
// Do whatever with MyObj
finally
MyObj.Free;
end;
Ясно, что этот блок кода отвечает за освобождение MyObj
, когда он закончил использовать.
MyObj := CreateMyObject(DBID);
Какой код должен освободить его? Когда вы можете безопасно освободить его? Кто отвечает за обработку исключений? Как вы знаете (как пользователь кода другого пользователя)?
Как правило, вы должны создавать, использовать и освобождать экземпляры объектов там, где они нужны. Это упрощает работу с вашим кодом и, безусловно, облегчает тем, кто приходит позже, и должен попытаться понять это.:)
Ответ 2
Одна проблема заключается в том, что написал Кен Уайт: вы передаете пользователю функцию, которую он или она должны освободить.
Другим преимуществом процедур является то, что вы можете передавать несколько объектов иерархии, а функция, создающая такой объект, всегда генерирует то же самое. Например.
procedure PopulateStrings(Strings: TStrings);
Для этой процедуры вы можете передавать любые типы TStrings, будь то строки TMemo, элементы TListBox или TComboBox или простой автономный TStringList. Если у вас есть функция:
function CreateStrings: TStrings;
Вы всегда получаете тот же тип объекта назад (какой объект точно неизвестен, так как TStrings abstract, поэтому вы, вероятно, получите TStringList) и должны назначить() содержимое в TStrings вы хотите изменить. Предпочтительная процедура, IMO.
Кроме того, если вы являетесь автором этой функции, вы не можете контролировать, освобожден ли создаваемый объект или когда. Если вы пишете процедуру, эта проблема снимается с ваших рук, так как пользователь предоставляет объект, а его срок службы не является для вас проблемой. И вам не обязательно знать точный тип объекта, он должен быть только класса или потомка параметра. IOW, это также намного лучше для автора функции.
ИМО редко дает хорошую идею вернуть объект из функции по всем приведенным причинам. процедура, которая только модифицирует объект, не имеет зависимости от объекта и не создает зависимости для пользователя.
FWIW, Другая проблема - если вы делаете это из DLL. Возвращаемый объект использует диспетчер памяти DLL, а также VMT, на который он указывает, находится в DLL. Это означает, что код, который использует as
или is
в коде пользователя, работает некорректно (поскольку is
и as
используют указатель VMT для проверки идентичности класса). Если пользователь должен передать объект своей, процедуре, эта проблема не возникает.
Update
Как отмечали другие, передача объекта в DLL не рекомендуется даже. Не виртуальные функции будут вызывать функции внутри DLL и использовать диспетчер памяти, что также может вызвать проблемы. И is
и as
тоже не будут корректно работать внутри DLL. Поэтому просто не передавайте объекты в DLL или из нее. Это связано с максимальным значением того, что DLL должны использовать только параметры POD type (или составные типы - массивы, записи - которые содержат только типы POD) или COM-интерфейсы. COM-интерфейсы также должны использовать только те же параметры.
Ответ 3
Я использую комбинацию обоих идиом. Передайте объект в качестве необязательного параметра и, если его не передать, создайте объект. И в любом случае вернуть объект как результат функции.
Этот метод имеет (1) гибкость создания объекта внутри вызываемой функции и (2) управление вызывающим абонентом вызывающего объекта, передающего объект в качестве параметра. Управление в двух значениях: управление в реальном типе используемого объекта и контроль момента, когда можно освободить объект.
Этот простой фрагмент кода иллюстрирует эту идиому.
function MakeList(aList:TStrings = nil):TStrings;
var s:TStrings;
begin
s:=aList;
if s=nil then
s:=TSTringList.Create;
s.Add('Adam');
s.Add('Eva');
result:=s;
end;
И вот три способа его использования
Простейшее использование, для быстрого и грязного кода
var sl1,sl2,sl3:TStrings;
sl1:=MakeList;
когда программист хочет сделать более явное владение и/или использовать настраиваемый тип
sl2:=MakeList(TMyStringsList.create);
когда объект ранее создан
sl3:=TMyStringList.Create;
....
MakeList(sl3);