Система Orphus

вторник, 10 августа 2010 г.

Волшебная сборка, часть 1

Это перевод A Magical Gathering – Part 1. Автор: Uwe Raabe.

Иногда комбинирование различных техник программирования раскрывает скрытую красоту языка программирования.

Несколько лет назад мне удалось создать такую вещь, соединяя некоторые старые и новые возможности языка. Конечный результат получился просто волшебным, он поразил меня.

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

4 комментарий(ев):

Лешик комментирует...

Вот мне интересна фраза

Мы уменьшили оригинальный код на две строки ценой добавления переменной, которая стоила нам строки (если вы не считаете строку на var, который почти наверняка был у вас и так).

но зато мы подключили, целый юнит, содержащий большее количество строк

GunSmoker комментирует...

Имеется ввиду, что модуль один раз уже написан. А циклов в коде - много.

Т.е. вопрос не в объёме exe в байтах, а в визуальном сокращении кода (меньше кода -> проще читать).

Это сильнее всего видно во второй части: замена DataSet.FieldValues['First_Name'] на DataSet.First_Name.

GunSmoker комментирует...

P.S. От себя замечу, что главная выгода в энумераторах - не в сокращении кода, а в уменьшении возможности ошибки (ошибиться с индексами граничных условий) и новых возможностях (это уже не к этой заметке).

Aleksey Timohin комментирует...

Хороший пример. Спасибо за перевод.


Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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