Иногда комбинирование различных техник программирования раскрывает скрытую красоту языка программирования.
Несколько лет назад мне удалось создать такую вещь, соединяя некоторые старые и новые возможности языка. Конечный результат получился просто волшебным, он поразил меня.
Рецепт очень прост: возьмите энумератор, подмешайте к нему класс-хэлпер и полейте этим invokeable custom вариант.
Чего?
Окей, забудьте это - давайте начнём с начала.
У меня была идея реализовать проход по набору данных каким-то простым способом, не используя неудобные конструкции вроде такой:
dataset.First; while not dataset.EOF do begin // сделать что-то с текущей записью dataset.Next; end;Я хотел иметь что-то более элегантное, например:
for data in dataset do begin // сделать что-то с data end;Поскольку TDataSet не имеет энумератора, я решил написать свой. Благо, проблем с этим нет - это всего несколько строк. Мой первый вариант выглядел так:
type
TDataSetEnumerator = class
private
FDataSet: TDataSet;
FCounter: Integer;
function GetCurrent: Integer;
public
constructor Create(ADataSet: TDataSet);
destructor Destroy; override;
function MoveNext: Boolean;
property Current: Integer read GetCurrent;
end;
constructor TDataSetEnumerator.Create(ADataSet: TDataSet);
begin
inherited Create;
FDataSet := ADataSet;
FDataSet.Open;
FCounter := -1; // указание, что мы хотим первую запись
end;
destructor TDataSetEnumerator.Destroy;
begin
FDataSet.Close;
end;
function TDataSetEnumerator.GetCurrent: Integer;
begin
Result := FCounter;
end;
function TDataSetEnumerator.MoveNext: Boolean;
begin
if FCounter < 0 then
begin
FDataSet.First;
FCounter := 0;
end
else
begin
FDataSet.Next;
Inc(FCounter);
end;
Result := not FDataSet.EoF;
end;
В тот раз идея использовать индекс текущей записи в качестве переменной для итерации казалась мне гениальной.
Но как же мне теперь втиснуть этот энумератор в набор данных? Класс-хэлперы спешат на помощь:
type
TDataSetHelper = class helper for TDataSet
public
function GetEnumerator: TDataSetEnumerator;
end;
function TDataSetHelper.GetEnumerator: TDataSetEnumerator;
begin
Result := TDataSetEnumerator.Create(Self);
end;Теперь я мог написать пробежку по набору данных так:
var
recIndex: Integer;
begin
for recIndex in dataset do
begin
// Делать что-то с текущей запиью
// Эй, и кстати, вы можете узнать индекс текущей записи из recIndex!
end;Так, первый шаг готов - комбинацией энумератора с классовым хэлпером.
Что мы получили? Мы уменьшили оригинальный код на две строки ценой добавления переменной, которая стоила нам строки (если вы не считаете строку на var, который почти наверняка был у вас и так). Нам нужно написать много циклов по наборам данных, чтобы перевесить размер кода для энумератора и классового хэлпера, не так ли?
Больше всего мне мешало, что тело цикла осталось без изменений. Все эти ссылки на поля записи:
DataSet.FieldValues['First_Name']
// или, если вы предпочитаете это:
DataSet.FieldByName('First_Name').Valueбыли раскиданы по всему коду. Я чувствовал, что я что-то упускаю.
Я ещё не достиг своей цели, и впереди лежит ещё более длинная дорога. Но подробнее о ней - во второй части…
Вот мне интересна фраза
ОтветитьУдалитьМы уменьшили оригинальный код на две строки ценой добавления переменной, которая стоила нам строки (если вы не считаете строку на var, который почти наверняка был у вас и так).
но зато мы подключили, целый юнит, содержащий большее количество строк
Имеется ввиду, что модуль один раз уже написан. А циклов в коде - много.
ОтветитьУдалитьТ.е. вопрос не в объёме exe в байтах, а в визуальном сокращении кода (меньше кода -> проще читать).
Это сильнее всего видно во второй части: замена DataSet.FieldValues['First_Name'] на DataSet.First_Name.
P.S. От себя замечу, что главная выгода в энумераторах - не в сокращении кода, а в уменьшении возможности ошибки (ошибиться с индексами граничных условий) и новых возможностях (это уже не к этой заметке).
ОтветитьУдалитьХороший пример. Спасибо за перевод.
ОтветитьУдалить