«Именованные параметры» в Delphi
Иногда возникает ситуация, когда в функцию требуется передавать много различных параметров, но при этом необходимый набор этих параметров может сильно различаться. В таких случаях, для Delphi, как правило, есть несколько путей решения:
- Просто забить все возможные параметры в интерфейс функции.
- Сделать множество перегрузок функции на все случаи жизни.
- Передать параметры массивом.
- Воспользоваться обходным путём. Например, вынести параметры в класс и проставлять их перед вызовом функции.
Всё эти способы получаются довольно громоздкими в реализации и имеют массу недостатков. А самое главное, что над их реализацией необходимо думать в каждом конкретном случае отдельно — не существует простого общего решения.
В некоторых языках (Scala, Python, Ruby…) такой проблемы не стоит в принципе — там есть такая замечательная вещь как именованные параметры. В Delphi же приходится всегда следовать установленному порядку аргументов. Не спасают даже значения по-умолчанию (их не всегда можно применить из-за конфликта типов, к тому же их использование нередко приводит к путанице).
Однако, используя небольшую хитрость, в Delphi вполне можно написать, к примеру, вот так:
ProcessParams(Par('Param1', 'test') + Par('Param2', 38) + Par('Param3', 3.2));
При этом в функцию ProcessParams придёт массив из трёх записей, содержащих пару «имя — значение». Такая запись становится возможной благодаря модулю объёмом всего 40 строк:
unit NamedParams; interface type TNamedParameter = record Name: string; Value: Variant; end; TNamedParameters = record Parameters: array of TNamedParameter; procedure AddParam(const ParamName: string; ParamValue: Variant); constructor Create(const ParamName: string; ParamValue: Variant); class operator Add(Parameter1, Parameter2: TNamedParameters): TNamedParameters; end; function Par(const ParamName: string; ParamValue: Variant): TNamedParameters; implementation function Par(const ParamName: string; ParamValue: Variant): TNamedParameters; begin Result := TNamedParameters.Create(ParamName, ParamValue); end; class operator TNamedParameters.Add(Parameter1, Parameter2: TNamedParameters): TNamedParameters; var TempParam: TNamedParameter; begin for TempParam in Parameter1.Parameters do Result.AddParam(TempParam.Name, TempParam.Value); for TempParam in Parameter2.Parameters do Result.AddParam(TempParam.Name, TempParam.Value); end; procedure TNamedParameters.AddParam(const ParamName: string; ParamValue: Variant); begin SetLength(Parameters, Length(Parameters) + 1); Parameters[ High(Parameters)].Name := ParamName; Parameters[ High(Parameters)].Value := ParamValue; end; constructor TNamedParameters.Create(const ParamName: string; ParamValue: Variant); begin AddParam(ParamName, ParamValue); end; end.
Немного расскажу о реализации. Функция Par в данном случае служит лишь обёрткой для конструктора объекта параметров. Благодаря ей, мы можем сделать использование метода максимально ёмким и простым. Сам же объект выполняет роль накопителя свойств, которые можно складывать между собой, благодаря перекрытию оператора сложения для самого объекта. Тип record выбран тоже не просто так. Во-первых, перекрытие операторов для классов работает только в .Net, а во-вторых, при такой реализации мы можем не напрягаться с освобождением памяти.
Использование этого метода передачи параметров в функции также предельно просто:
procedure ProcessParams(Params: TNamedParameters); const ParamStr = '%s = %s'#13#10; var TempParam: TNamedParameter; Str: string; begin Str := ''; for TempParam in Params.Parameters do begin if VarIsFloat(TempParam.Value) then Str := Str + Format(ParamStr, [TempParam.Name, FloatToStr(TempParam.Value)]) else if VarIsNumeric(TempParam.Value) then Str := Str + Format(ParamStr, [TempParam.Name, IntToStr(TempParam.Value)]) else Str := Str + Format(ParamStr, [TempParam.Name, TempParam.Value]); end; ShowMessage(Str); end;
Пример вызова данной функции был описан в начале статьи.
Как видите, этот метод позволяет легко и не задумываясь, а главное наглядно, передавать в функцию любой массив параметров одним выражением.