Иногда комбинирование различных техник программирования раскрывает скрытую красоту языка программирования.
Несколько лет назад мне удалось создать такую вещь, соединяя некоторые старые и новые возможности языка. Конечный результат получился просто волшебным, он поразил меня.
Рецепт очень прост: возьмите энумератор, подмешайте к нему класс-хэлпер и полейте этим 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. От себя замечу, что главная выгода в энумераторах - не в сокращении кода, а в уменьшении возможности ошибки (ошибиться с индексами граничных условий) и новых возможностях (это уже не к этой заметке).
ОтветитьУдалитьХороший пример. Спасибо за перевод.
ОтветитьУдалить