Придумка Фаулера (http://18delphi.blogspot.com/2013/09/blog-post_4.html) - конечно - ХОРОША.
Но не в переложении русскоязычного автора - http://18delphi.blogspot.com/2013/09/blog-post_3.html.
Русскоязычный автор "потерял" в своей статье использование new. И из-за этого - "смазал" всю идею Фаулера.
Я САМ таким - ДАВНО пользуюсь. Ещё задолго до Фаулера.
Меня тут спросили - "как я этим пользуюсь".
Да примерно также как и Фаулер.
Примерно в таком ключе:
TTreeBuilder.Make(Tree)
.AddNode('Text1')
.AddNode('Text2')
.AddNode('Text3', TSpecialNodeType1)
.AddNode('Text4', TSpecialNodeType2)
.OpenLevel
.AddNode('Sibling 1')
.AddNode('Sibling 2')
.AddNode('Sibling 3')
.OpenLevel
.AddNode('SubSibling 1')
.CloseLevel
.CloseLevel
.AddNode('Text 5');
- я сам это придумал. Задолго до Фаулера.
И не считаю это "революционным". Я считаю это "эволюцией" цепочечных выражений вида:
DoParams(TParamsList.Create.AddParam("Param1").AddParam(123).AddParam(TClass.Create).AddParam("ParamX"))
Более того, я считаю и это эволюцией идеи Кернигана и Ричи. В виде:
cout << "Hello, man" << "you're number" << 16 << "in the list of less than " << 100.5 << endln;
О чём и написал Всеволод Леонов - http://18delphi.blogspot.com/2013/09/blog-post_3.html?showComment=1378273720721#c6925185551184111953
Хотя некоторые оппоненты и считают, что "пример Леонова не к месту".
Я считаю, что пример Леонова - УМЕСТЕН и грамматически и семантически правильный.
Что до того, что "сам придумал" - не спрашивайте меня proof-link'и. Их - НЕТУ. Придётся "поверить на слово". А я обычно - слово держу.
Я по молодости - статей не писал. А - зря. Сейчас - восполняю этот пробел.
Я САМ много чего "придумал" и SAX, и XML, и XPath и Publisher/Subscriber.
И IUnknown и "подсчёт ссылок".
Это конечно - выглядит "голословным", но это - так.
У меня есть коллеги, которые могут подтвердить. Если захотят. Ну а если не захотят.. То - не подтвердят. Имеют право...
Что до fluent interface'ов, то как ПРАВИЛЬНО заметили - они УДОБНО и БЕЗОПАСНО реализуются - ТОЛЬКО через ИНТЕРФЕЙСЫ (ключевое слово - interface).
Я же - привык - "считать копейки".
Я ОБЫЧНО - пишу код, а потом открываю отладчик и смотрю на его ассемблерный аналог.
В наш век "гигабайтов и гигагерц" это наверное выглядит - "причудой". Ну что же. Считайте меня - старомодным занудой.
Есть ОДИН БОЛЬШОЙ минус в fluent interface'ах. КАЖДЫЙ возврат интерфейса из функции ведёт к однократному, а то и двукратному вызову AddRef/Release.
А они - недёшевы. С учётом требований "атомарности".
Посему - я сам - стараюсь отходить от fluent interface'ов.
(Ну и не только поэтому).
Я сам сторонник "классического" подхода. "Данные и алгоритмы". Вирт.
Отдельно ДЕКЛАРАТИВНО описываем структуру, а отдельно ИМПЕРАТИВНО - алгоритм генерации экземпляров объектов по этой структуре.
И я продолжаю склоняться к этой парадигме.
А так - идея Фаулера - хороша. Только ничего "молодого" и "революционного" я в ней не вижу.
Но это - НЕ ВАЖНО.
Но не в переложении русскоязычного автора - http://18delphi.blogspot.com/2013/09/blog-post_3.html.
Русскоязычный автор "потерял" в своей статье использование new. И из-за этого - "смазал" всю идею Фаулера.
Я САМ таким - ДАВНО пользуюсь. Ещё задолго до Фаулера.
Меня тут спросили - "как я этим пользуюсь".
Да примерно также как и Фаулер.
Примерно в таком ключе:
TTreeBuilder.Make(Tree)
.AddNode('Text1')
.AddNode('Text2')
.AddNode('Text3', TSpecialNodeType1)
.AddNode('Text4', TSpecialNodeType2)
.OpenLevel
.AddNode('Sibling 1')
.AddNode('Sibling 2')
.AddNode('Sibling 3')
.OpenLevel
.AddNode('SubSibling 1')
.CloseLevel
.CloseLevel
.AddNode('Text 5');
- я сам это придумал. Задолго до Фаулера.
И не считаю это "революционным". Я считаю это "эволюцией" цепочечных выражений вида:
DoParams(TParamsList.Create.AddParam("Param1").AddParam(123).AddParam(TClass.Create).AddParam("ParamX"))
Более того, я считаю и это эволюцией идеи Кернигана и Ричи. В виде:
cout << "Hello, man" << "you're number" << 16 << "in the list of less than " << 100.5 << endln;
О чём и написал Всеволод Леонов - http://18delphi.blogspot.com/2013/09/blog-post_3.html?showComment=1378273720721#c6925185551184111953
Хотя некоторые оппоненты и считают, что "пример Леонова не к месту".
Я считаю, что пример Леонова - УМЕСТЕН и грамматически и семантически правильный.
Что до того, что "сам придумал" - не спрашивайте меня proof-link'и. Их - НЕТУ. Придётся "поверить на слово". А я обычно - слово держу.
Я по молодости - статей не писал. А - зря. Сейчас - восполняю этот пробел.
Я САМ много чего "придумал" и SAX, и XML, и XPath и Publisher/Subscriber.
И IUnknown и "подсчёт ссылок".
Это конечно - выглядит "голословным", но это - так.
У меня есть коллеги, которые могут подтвердить. Если захотят. Ну а если не захотят.. То - не подтвердят. Имеют право...
Что до fluent interface'ов, то как ПРАВИЛЬНО заметили - они УДОБНО и БЕЗОПАСНО реализуются - ТОЛЬКО через ИНТЕРФЕЙСЫ (ключевое слово - interface).
Я же - привык - "считать копейки".
Я ОБЫЧНО - пишу код, а потом открываю отладчик и смотрю на его ассемблерный аналог.
В наш век "гигабайтов и гигагерц" это наверное выглядит - "причудой". Ну что же. Считайте меня - старомодным занудой.
Есть ОДИН БОЛЬШОЙ минус в fluent interface'ах. КАЖДЫЙ возврат интерфейса из функции ведёт к однократному, а то и двукратному вызову AddRef/Release.
А они - недёшевы. С учётом требований "атомарности".
Посему - я сам - стараюсь отходить от fluent interface'ов.
(Ну и не только поэтому).
Я сам сторонник "классического" подхода. "Данные и алгоритмы". Вирт.
Отдельно ДЕКЛАРАТИВНО описываем структуру, а отдельно ИМПЕРАТИВНО - алгоритм генерации экземпляров объектов по этой структуре.
И я продолжаю склоняться к этой парадигме.
А так - идея Фаулера - хороша. Только ничего "молодого" и "революционного" я в ней не вижу.
Но это - НЕ ВАЖНО.
Использование интерфейсов в подобных выражениях ведет к утечке памяти в Delphi XE2 и ранее, в более поздних не смотрел.
ОтветитьУдалитьК утечке приводит тот факт, что при вызове функций цепочкой не вызывается _release.
_AddRef вызывается в момент присвоения значения псевдопеременной Result, а _Release - в момент присвоения значения той переменной, в которую мы положим результат вызова функции. Если же этот результат мы не присваиваем переменной, то _Кудуфыу не будет вызван вовсе.
Полный код примера.
program fit;
//сделана для ответа на этот пост
//http://18delphi.blogspot.ru/2013/09/fluent-interface.html
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
ITFI=interface(IUnknown)
function fi:ITFI;
end;
TTFI=class(TInterfacedObject,ITFI)
function fi:ITFI;
end;
{ TTFI }
function TTFI.fi: ITFI;
begin
Writeln('Before: ',self.FRefCount);
Result := Self;
Writeln('After: ',self.FRefCount);
end;
var
f:ITFI;
begin
try
f:=ttfi.create;
//f.FRefCount=1
f:=f.fi;
//f.FRefCount=2
f:=f.fi.fi.fi.fi;
//f.FRefCount=6
f:=nil;
//единственный вызов f._release Интересно, что же в счетчике ссылок?
readln;//чтоб с экрана вывод не пропадал
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
NameRec:
Удалить«Использование интерфейсов в подобных выражениях ведет к утечке памяти в Delphi XE2 и ранее, в более поздних не смотрел.»
-- Не подтверждаю.
Попробуйте запустить такой вариант:
{code}
{$apptype console}
program fit;
uses
SysUtils;
type
ITFI = interface(IUnknown)
function fi: ITFI;
procedure Test(Prompt: String='');
end;
TTFI = class(TInterfacedObject, ITFI)
public
destructor Destroy; override;
function fi: ITFI;
procedure Test(Prompt: String='');
end;
{ TTFI }
destructor TTFI.Destroy;
begin
Test('Destroy');
inherited;
end;
function TTFI.fi: ITFI;
begin
Result := Self;
Test('fi');
end;
procedure TTFI.Test(Prompt: String='');
begin
if Prompt <> '' then
Prompt := '[' + Prompt + '] ';
WriteLn(Prompt, Self.RefCount);
end;
procedure Execute;
var
f, f1: ITFI;
begin
WriteLn('begin Execute');
f := ttfi.create;
f.Test('after ttfi.create');
f := f.fi;
f.Test('after f.fi');
f := f.fi.fi.fi.fi;
f.Test('after f := f.fi.fi.fi.fi');
Move(f, f1, SizeOf(f));
f := nil;
f1.Test('after f := nil');
WriteLn('end Execute');
end;
begin
WriteLn('begin program');
try
WriteLn('before call Execute');
Execute;
WriteLn('after call Execute');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
WriteLn('end program');
end.
{/code}
По меньшей мере, в Delphi 2007 память освобождается корректно.
Вызов TTFI.Destroy происходит после завершения процедуры Execute.
Кстати, как Вы определили, что утечка памяти имеет место?
У меня - нет проблем с _AddRef/_Release. У меня есть тесты для DUnit.
УдалитьНо я НЕ ИСПОЛЬЗУЮ TInterfacedObject. Я уже писал об этом. Может быть проблема в этом.
Хотя - вряд ли.
Delphi 7, XE3, XE4
УдалитьА я вам скажу в чём проблема. Для глобальных переменных уровня programm есть проблемы с compiler-magic и Release. Это как раз - проверено.
УдалитьЯ вот "по-стариковски" даже НЕ ДУМАЛ, что это такая ЖИВОТРЕПЕЩУЩАЯ тема.
ОтветитьУдалитьЯ просто "по ходу жизни" откомментировал.
А тут ТАКАЯ жизнь!
Но мне лично - СИЛЬНО интереснее - UML, DSL и TDD. Почему-то...
Ну или "хардкорные" обёртки над локальными функциями...
А "синтаксический сахар" - далеко не всегда интересен....
NameRec:
Удалить«Я вот "по-стариковски" даже НЕ ДУМАЛ, что это такая ЖИВОТРЕПЕЩУЩАЯ тема.»
-- В комментариях - обсуждение возможной ошибки Delphi при работе с интерфейсами в контексте потомков TInterfacedObject.
IMHO - тема, как минимум, заслуживающая внимания.
Проблемы с TInterfacedObject - да.
УдалитьNameRec:
ОтветитьУдалить«Но мне лично - СИЛЬНО интереснее - UML, DSL и TDD. Почему-то...
Ну или "хардкорные" обёртки над локальными функциями...
А "синтаксический сахар" - далеко не всегда интересен....»
-- Александр, а Вы не находите, что fluent-техника, в сущности, DSL и есть? Она выглядит как DSL и правила работы с ней напоминают правила работы с DSL.
Только DSL в контексте существующего языка.
Вероятно, это не просто увидеть, поскольку обычно DSL *реализуется* средствами другого языка.
fluent-техника - не синтаксический сахар в классическом понимании (http://ru.wikipedia.org/wiki/Синтаксический_сахар), поскольку не требует расширения синтаксиса языка программирования, в чём я вижу достоинство.
Fluent-техника действительно очень похожа на DSL как по реализации, так и по применению.
Поверхностно проверить моё утверждение можно относительно просто.
Выше я говорил, что вижу ограниченную область применения этой техники в Delphi и обозначил описание SQL - как пример задачи, где fluent-техника может принести пользу.
Учитывая, что SQL, в сущности, является DSL, можно посмотреть, получится ли использовать fluent-технику для описания объектами других известных DSL. Таких как XML, XPath, regexp, JSON и, не поверите, даже UML.
Понятное дело, что генерация UML-диаграммы в программном коде выглядит, по меньшей мере, странно. Но технически это возможно.
Для описания же XML, XPath, regexp и т.п. вполне можно найти достойные применения.
Что делает это возможным? Наличие у языка описания, что ограничивает возможное число вариантов в контексте построения fluent-цепочки.
Разумеется, никого не призываю "выкидывать свои DSL" и "прикручивать" вместо них текучие интерфейсы.
Просто предлагаю под другим углом посмотреть на технику, которая (есть у меня такое ощущение) может оказаться полезной там, где требуется объектное описание *существующего* DSL.
NameRec:
ОтветитьУдалитьПример где это может оказаться востребованным.
--
Ряд СУБД (Oracle, Postgres, SQLite, Red Database (клон Firebird)), поддерживают полнотекстовый поиск, но при сходных возможностях, язык запросов в них заметно отличается.
Предположим, мы хотим:
1. Обеспечить независимое от СУБД хранение полнотекстовых запросов
2. Предоставить пользователю возможность делать полнотекстовые запросы интерактивно и хотим его избавить от набора магических строчек вида «сверхпроводимость -"вики" -"яндекс" +"левитация" +"видео"», поскольку все эти плюсы-минусы и кавычки не интуитивны для неподготовленного пользователя.
Нам потребуется создать интерфейс пользователя для формирования полнотекстового запроса. Вероятно, центральным элементом интерфейса будет древовидное представление запроса, поскольку в нём возможны скобки логические условия (AND, OR...).
Ввиду [1], вероятно, потребуется разработать свой диалект синтаксиса для полнотекстовых запросов, который будет при исполнении транслироваться в строку для полнотекстового запроса к используемой СУБД.
Ввиду [2] удобным способом описания языка будет набор соответствующих объектов.
Таким образом, для сохранения полнотекстового запроса, сформированного пользователем с использованием интерактивных средств будет сериализация построенной совокупности объектов.
Выше была преамбула. Теперь — амбула :-)
Как быть, если мы хотим построить запрос полнотекстового поиска в коде? Разумеется, отвлекаясь от специфики СУБД, в контексте которой он будет исполняться.
Очевидно, нам следует прибегнуть к нашему диалекту языка полнотекстовых запросов. По-простому — построить совокупность объектов.
Как это сделать проще всего? - Что если попробовать fluent-технику?
{code}
_ // сверхпроводимость -"вики" -"яндекс" +"левитация" +"видео"
_ full_text_req := NewFullTextRequest()
_ _ .AND_Bracket([
_ _ _ FullText_Words(['сверхпроводимость']),
_ _ _ FullText_Minus(['вики', 'яндекс']),
_ _ _ FullText_Plus(['левитация', 'видео'])
_ _ ]);
{/code}
Что же здесь общего с DSL? - Кое-что есть :-)
Например, проверку синтаксиса нашего «диалекта» языка запросов мы возлагаем на компилятор Delphi.
Так, NewFullTextRequest возвращает интерфейс IFullTextRequest, содержащий методы AND_Bracket и OR_Bracket – скобки для условий поиска, элементы которых объединены условиями, соответственно, AND или OR.
Подобно этому, AND/OR_Bracket принимают в качестве параметра массив элементов типа IFullTextCondition, которые умеют создавать функции FullText_Words/Minus/Plus, которые, в свою очередь, принимают на вход массивы строк, посредством которых описываются условия.
Далее, если потребуется расширить наш язык новыми понятиями, мы можем следовать парадигме: определить место в существующей структуре, в которой востребованы новые понятия и поддержать соответствующие fluent-методы.
Например: новый вид условий XOR можно поддержать «рядом» с AND/OR_Bracket.
Отвечу на один вопрос из множества, которые могут появиться.
[Q] Зачем «городить огород» с описанием диалекта посредством объектов? Почему не парсить строчку, оговорив предварительно её формат? Т.е. сформулировав диалект.
[A] Например, по следующим причинам:
1. При изменениях в языке, запросы, описанные в коде в виде строчек приведут к ошибкам при выполнении, а записанные во fluent-технике к ошибкам компиляции.
2. Работать с объектами проще, чем со строками, поскольку пропадает потребность в разборе строк. А обрабатывать запрос, сформулированный в нашем диалекте придётся перед его выполнением средствами используемой СУБД.
3. Объекты, вероятно, всё равно потребуются ввиду [2] технических требований. Поэтому, строки можно рассматривать как формат сериализации этих объектов, но если из технических соображений сделать объекты потомками TComponent, «из коробки» станет доступна и сериализация.
Ладно, здесь я, пожалуй, остановлюсь. Поскольку у меня нет уверенности в том, что развитие этой темы здесь кому-либо может оказаться интересным...
А такой подход - мне нравится. Спасибо!
Удалить