четверг, 11 июля 2013 г.

Абстрактные контейнеры

Подготовительные серии были тут - http://18delphi.blogspot.com/2013/07/1.html

Теперь хочется рассказать о практике создания "шаблонных" контейнеров в "стиле STL".

ПОКА - БЕЗ "настоящих" Generic'ов (http://18delphi.blogspot.ru/2013/03/generic-generic.html). Но и их - ТОЖЕ можно использовать. Но ПОКА - есть МНОЖЕСТВО НЕЗАКРЫТЫХ ошибок, которые Embarcadero почему-то не спешит закрывать. Минус ребятам. Что сказать. Даже мою ошибку (http://18delphi.blogspot.com/2013/05/resolved.html http://18delphi.blogspot.com/2013/05/xe4.html) - "типа исправили", но не закрыли. Хотя там - "копейки". Если я своим умом - всё правильно понимаю...

Одна ремарка. Несмотря на то, что в STL подобные контейнеры называются vector - я решил сохранить преемственность с Delphi и назвал их List.

Итак. Как обычно.

Модель:

Код:

List.imp.pas:

{$IfNDef List_imp}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBox"
// Модуль: "List.imp.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: Impurity::Class Shared Delphi Sand Box::SandBox::STLLike::List
//
// Абстрактный список значений
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

{$Define List_imp}
 PItemType = ^_ItemType_;

const
  { Sizes }
 cItemSize = SizeOf(_ItemType_);

type
 IndexType = System.Integer;

 _List_ = {mixin} class(_List_Parent_)
  {* Абстрактный список значений }
 private
 // private fields
   f_Data : Tl3PtrLoc;
   f_Count : IndexType;
    {* Поле для свойства Count}
 private
 // private methods
   procedure ReAllocList(aNewCapacity: IndexType);
   procedure CheckIndex(anIndex: IndexType); // can raise EListError
     {* проверяет валидность индекса и поднимает исключение, если он неправильный }
   function ItemSlot(anIndex: IndexType): PItemType;
   function ExpandSize(aTargetSize: IndexType): Cardinal;
   procedure CheckSetItem(anIndex: IndexType); // can raise EListError
     {* Проверяет валидность индекса при вставке }
 protected
 // property methods
   procedure pm_SetCount(aValue: IndexType);
   function pm_GetCapacity: IndexType;
   procedure pm_SetCapacity(aValue: IndexType);
   function pm_GetItems(anIndex: IndexType): _ItemType_;
   procedure pm_SetItems(anIndex: IndexType; const aValue: _ItemType_);
 protected
 // overridden protected methods
   procedure Cleanup; override;
     {* Функция очистки полей объекта. }
 public
 // public properties
   property Count: IndexType
     read f_Count
     write pm_SetCount;
   property Capacity: IndexType
     read pm_GetCapacity
     write pm_SetCapacity;
   property Items[anIndex: IndexType]: _ItemType_
     read pm_GetItems
     write pm_SetItems;
     default;
     {* Элементы списка. }
 end;//_List_

{$Else List_imp}

// start class _List_

procedure _List_.ReAllocList(aNewCapacity: IndexType);
//#UC START# *51DEB8770017_51DEB07E03E4_var*
var
 l_Cap : Integer;
 l_Cnt : Integer;
//#UC END# *51DEB8770017_51DEB07E03E4_var*
begin
//#UC START# *51DEB8770017_51DEB07E03E4_impl*
 f_Data.SetSize(aNewCapacity * cItemSize);
 l_Cap := Self.Capacity;
 Assert(l_Cap >= aNewCapacity);
 l_Cnt := f_Count;
 if (l_Cap > l_Cnt) then
  System.FillChar(ItemSlot(l_Cnt)^, (l_Cap - l_Cnt) * cItemSize, 0);
//#UC END# *51DEB8770017_51DEB07E03E4_impl*
end;//_List_.ReAllocList

procedure _List_.CheckIndex(anIndex: IndexType); // can raise EListError
//#UC START# *51DEB95E00BD_51DEB07E03E4_var*

 procedure _Error;
 begin
  raise EListError.CreateFmt(SListIndexError + ' from (%d)', [anIndex, f_Count])
 end;

//#UC END# *51DEB95E00BD_51DEB07E03E4_var*
begin
//#UC START# *51DEB95E00BD_51DEB07E03E4_impl*
 if (anIndex < 0) or (anIndex >= f_Count) then
  _Error;
//#UC END# *51DEB95E00BD_51DEB07E03E4_impl*
end;//_List_.CheckIndex

function _List_.ItemSlot(anIndex: IndexType): PItemType;
//#UC START# *51DEBE2D008A_51DEB07E03E4_var*
//#UC END# *51DEBE2D008A_51DEB07E03E4_var*
begin
//#UC START# *51DEBE2D008A_51DEB07E03E4_impl*
 Result := PItemType(f_Data.AsPointer + anIndex * cItemSize);
 assert(Result <> nil);
//#UC END# *51DEBE2D008A_51DEB07E03E4_impl*
end;//_List_.ItemSlot

function _List_.ExpandSize(aTargetSize: IndexType): Cardinal;
//#UC START# *51DEC11F0058_51DEB07E03E4_var*
const
 cIncrArray : array [0..3] of Integer = (64 * 1024, 1024, 128, 4);
 cMaxForTwice : Integer = 1 * 1024 * 1024;
var
 I : Integer;
//#UC END# *51DEC11F0058_51DEB07E03E4_var*
begin
//#UC START# *51DEC11F0058_51DEB07E03E4_impl*
 Assert(aTargetSize > 0);

 Result := aTargetSize;
 if (Result > cMaxForTwice) then
 // большие массивы не удваиваем а подравниваем под 1мб
  Result := (aTargetSize div cMaxForTwice + 1) * cMaxForTwice
 else
 begin
  for I := 0 to High(cIncrArray) do
   if (aTargetSize > cIncrArray[I]) then
   begin
    Result := (aTargetSize div cIncrArray[I]) * cIncrArray[I] * 2;
    Break;
   end;//aTargetSize > cIncrArray[I]
 end;//Result > cMaxForTwic
//#UC END# *51DEC11F0058_51DEB07E03E4_impl*
end;//_List_.ExpandSize

procedure _List_.CheckSetItem(anIndex: IndexType); // can raise EListError
//#UC START# *51DECAA8035E_51DEB07E03E4_var*
//#UC END# *51DECAA8035E_51DEB07E03E4_var*
begin
//#UC START# *51DECAA8035E_51DEB07E03E4_impl*
 CheckIndex(anIndex);
//#UC END# *51DECAA8035E_51DEB07E03E4_impl*
end;//_List_.CheckSetItem

procedure _List_.pm_SetCount(aValue: IndexType);
//#UC START# *51DEB1ED0017_51DEB07E03E4set_var*

 procedure SayBadCount(aNewCount: LongInt);
 begin
  raise EListError.CreateFmt(sListIndexError, [aNewCount]);
 end;

var
 l_Ptr   : PItemType;
 {$IfNDef l3Items_IsUnrefcounted}
 l_Index : Integer;
 {$EndIf  l3Items_IsUnrefcounted}
//#UC END# *51DEB1ED0017_51DEB07E03E4set_var*
begin
//#UC START# *51DEB1ED0017_51DEB07E03E4set_impl*
 if (aValue < 0) then
  SayBadCount(aValue);
 if (aValue < f_Count) then
 begin
  l_Ptr := ItemSlot(aValue);
  {$IfDef l3Items_IsUnrefcounted}
  System.FillChar(l_Ptr^, (f_Count - 1 - aValue) * cItemSize, 0);
  {$Else  l3Items_IsUnrefcounted}
  for l_Index := aValue to f_Count - 1 do
  begin
   FreeItem(l_Ptr^);
   Inc(PMem(l_Ptr), cItemSize);
  end;//for i
  {$EndIf  l3Items_IsUnrefcounted}
 end//aValue < f_Count
 else
 if (aValue > Self.Capacity) then
  ReAllocList(ExpandSize(aValue));
 if (f_Count < aValue) then
  System.FillChar(ItemSlot(f_Count)^, (aValue - f_Count) * cItemSize, 0);
 f_Count := aValue;
//#UC END# *51DEB1ED0017_51DEB07E03E4set_impl*
end;//_List_.pm_SetCount

function _List_.pm_GetCapacity: IndexType;
//#UC START# *51DEB20E0130_51DEB07E03E4get_var*
//#UC END# *51DEB20E0130_51DEB07E03E4get_var*
begin
//#UC START# *51DEB20E0130_51DEB07E03E4get_impl*
 Result := f_Data.GetSize div cItemSize;
//#UC END# *51DEB20E0130_51DEB07E03E4get_impl*
end;//_List_.pm_GetCapacity

procedure _List_.pm_SetCapacity(aValue: IndexType);
//#UC START# *51DEB20E0130_51DEB07E03E4set_var*

 procedure SayBadCap(aNewCapacity: IndexType);
 begin
  raise EListError.CreateFmt(sListIndexError, [aNewCapacity]);
 end;

//#UC END# *51DEB20E0130_51DEB07E03E4set_var*
begin
//#UC START# *51DEB20E0130_51DEB07E03E4set_impl*
 if (aValue < 0) then
  SayBadCap(aValue);
 if (pm_GetCapacity <> aValue) then
 begin
  { If the list is shrinking, then update _Count for the smaller size. }
  if (aValue < f_Count) then
   Count := aValue;
  ReAllocList(aValue);
 end;//GetCapacity(Self) <> aValue
//#UC END# *51DEB20E0130_51DEB07E03E4set_impl*
end;//_List_.pm_SetCapacity

function _List_.pm_GetItems(anIndex: IndexType): _ItemType_;
//#UC START# *51DECA1202C5_51DEB07E03E4get_var*
//#UC END# *51DECA1202C5_51DEB07E03E4get_var*
begin
//#UC START# *51DECA1202C5_51DEB07E03E4get_impl*
 CheckIndex(anIndex);
 Result := ItemSlot(anIndex)^;
//#UC END# *51DECA1202C5_51DEB07E03E4get_impl*
end;//_List_.pm_GetItems

procedure _List_.pm_SetItems(anIndex: IndexType; const aValue: _ItemType_);
//#UC START# *51DECA1202C5_51DEB07E03E4set_var*
{$IfNDef l3Items_IsAtomic}
var
 l_P : PItemType;
{$EndIf  l3Items_IsAtomic}
//#UC END# *51DECA1202C5_51DEB07E03E4set_var*
begin
//#UC START# *51DECA1202C5_51DEB07E03E4set_impl*
 CheckSetItem(anIndex);
 {$IfDef l3Items_IsAtomic}
 PItemType(ItemSlot(anIndex))^ := aValue;
 {$Else  l3Items_IsAtomic}
 l_P := PItemType(ItemSlot(anIndex));
 if not IsSame(l_P^, aValue) then
 begin
  FreeItem(l_P^);
  FillItem(l_P^, aValue);
 end;//not IsSame(l_P^, anItem)
 {$EndIf l3Items_IsAtomic}
//#UC END# *51DECA1202C5_51DEB07E03E4set_impl*
end;//_List_.pm_SetItems

procedure _List_.Cleanup;
//#UC START# *479731C50290_51DEB07E03E4_var*
//#UC END# *479731C50290_51DEB07E03E4_var*
begin
//#UC START# *479731C50290_51DEB07E03E4_impl*
 Count := 0;
 f_Data.SetSize(0);
 inherited;
//#UC END# *479731C50290_51DEB07E03E4_impl*
end;//_List_.Cleanup

{$EndIf List_imp}

UnrefcountedListPrim.imp.pas:

{$IfNDef UnrefcountedListPrim_imp}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBox"
// Модуль: "UnrefcountedListPrim.imp.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: Impurity::Class Shared Delphi Sand Box::SandBox::STLLike::UnrefcountedListPrim
//
// Список значений без какого то бы ни было подсчёта ссылок
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

{$Define UnrefcountedListPrim_imp}
 {$Define l3Items_IsUnrefcounted}

 _List_Parent_ = _UnrefcountedListPrim_Parent_;
 {$Include List.imp.pas}
 _UnrefcountedListPrim_ = {mixin} class(_List_)
  {* Список значений без какого то бы ни было подсчёта ссылок }
 end;//_UnrefcountedListPrim_

{$Else UnrefcountedListPrim_imp}

// start class _UnrefcountedListPrim_

function IsSame(const A: _ItemType_;
  const B: _ItemType_): Boolean;
//#UC START# *51DECB820261_51DED02E0163_var*
//#UC END# *51DECB820261_51DED02E0163_var*
begin
//#UC START# *51DECB820261_51DED02E0163_impl*
 Result := (A = B);
//#UC END# *51DECB820261_51DED02E0163_impl*
end;//IsSame

type _List_R_ = _UnrefcountedListPrim_;

{$Include List.imp.pas}


{$EndIf UnrefcountedListPrim_imp}

AtomicList.imp.pas:

{$IfNDef AtomicList_imp}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBox"
// Модуль: "AtomicList.imp.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: Impurity::Class Shared Delphi Sand Box::SandBox::STLLike::AtomicList
//
// Список атомарных значений
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

{$Define AtomicList_imp}
 {$Define l3Items_IsAtomic}

 _UnrefcountedListPrim_Parent_ = _AtomicList_Parent_;
 {$Include UnrefcountedListPrim.imp.pas}
 _AtomicList_ = {mixin} class(_UnrefcountedListPrim_)
  {* Список атомарных значений }
 end;//_AtomicList_

{$Else AtomicList_imp}

// start class _AtomicList_

procedure FillItem(var thePlace: _ItemType_;
  const aFrom: _ItemType_); forward;

procedure FreeItem(var thePlace: _ItemType_);
//#UC START# *51DEC20B01D7_51DED48301D9_var*
//#UC END# *51DEC20B01D7_51DED48301D9_var*
begin
//#UC START# *51DEC20B01D7_51DED48301D9_impl*
 thePlace := _ItemType_(0);
//#UC END# *51DEC20B01D7_51DED48301D9_impl*
end;//FreeItem

procedure FillItem(var thePlace: _ItemType_;
  const aFrom: _ItemType_);
//#UC START# *51DECB440087_51DED48301D9_var*
//#UC END# *51DECB440087_51DED48301D9_var*
begin
//#UC START# *51DECB440087_51DED48301D9_impl*
 thePlace := aFrom;
//#UC END# *51DECB440087_51DED48301D9_impl*
end;//FillItem

type _UnrefcountedListPrim_R_ = _AtomicList_;

{$Include UnrefcountedListPrim.imp.pas}


{$EndIf AtomicList_imp}

Код тут - http://sourceforge.net/p/rumtmarc/code-0/19/tree/trunk/Blogger/SandBox и http://sourceforge.net/p/rumtmarc/code-0/19/tree/trunk/Blogger/SandBoxTest

13 комментариев:

  1. NameRec:
    Вызывает уважение.
    Особенно, если учесть, что код сгенерирован из UML-диаграммы.
    1. Правильно ли я понимаю, что ваш (Вашей организации) инструмент UML-моделирования поддерживает возможность при определении методов в классе на UML-модели сразу же указывать код реализации?
    На эту мысль наводят пары #UC START# - #UC END#, ну и группировка локальных процедур в #UC START# *..._var* - #UC END# *..._var*.
    Или, реализация методов лежит отдельно от UML-диаграммы?
    2. Относительно реализации контейнеров. Вы не смотрели SDL/DeCAL - библиотеке, о которой рассказывал (http://gurin.tomsknet.ru/delphidecal.html) Уважаемый мною Сергей Гурин?

    ОтветитьУдалить
    Ответы
    1. "Вызывает уважение."

      Спасибо :-)

      "Или, реализация методов лежит отдельно от UML-диаграммы?"

      И да и нет :-) В данном случае "пары #UC START# - #UC END#" - это как раз "маркеры" - "вот сюда впишите код".

      Удалить
    2. "Относительно реализации контейнеров. Вы не смотрели SDL/DeCAL"

      Стыдно признаться. Но. Я о ней - не знал.

      Посмотрю внимательно.

      Но вот это мне знакомо:

      TObject0 = class
      public
      I: Integer;
      constructor Create(aI: Integer);
      {$IFDEF TTUSEPOOL}
      class function NewInstance : TObject; override;
      procedure FreeInstance; override;
      {$ENDIF}
      end;

      TTUSEPOOL !!!!!
      -- я САМ такое тоже придумал :-)

      Удалить
    3. «В данном случае "пары #UC START# - #UC END#" - это как раз "маркеры" - "вот сюда впишите код".»
      -- Таким образом, из UML генерируется структура класса, GUID в #UC START# *...*, как я понимаю, идентификатор метода в UML-редакторе? Вероятно, назначается автоматически...
      Тэги #UC START# - #UC END# используются при генерации кода из UML-модели при её (модели) изменении для того, чтобы не потерять существующую реализацию. Я правильно понимаю?

      «TTUSEPOOL !!!!!
      -- я САМ такое тоже придумал :-)»
      -- Да, там есть условно-поключаемая реализация хранения служебных структур в своём собственном пуле RAM. Мы отключаем - FastMM работает достаточно эффективно...
      Кстати, есть порт DeCAL к XE-версиям Delphi.

      Удалить
    4. "как я понимаю, идентификатор метода в UML-редакторе? Вероятно, назначается автоматически..."

      Именно. Автоматически.

      "Тэги #UC START# - #UC END# используются при генерации кода из UML-модели при её (модели) изменении для того, чтобы не потерять существующую реализацию. Я правильно понимаю?"

      Всё правильно.

      Одна ремарка. Бывает (если это описано в шаблоне генерации), что код - тоже "сам" генерируется. Тогда как раз он тегами - не окружается.

      "Да, там есть условно-поключаемая реализация хранения служебных структур в своём собственном пуле RAM"

      Знакомо :-)

      "Мы отключаем - FastMM работает достаточно эффективно..."
      Я знаю :-) FastMM - хорош.

      "Кстати, есть порт DeCAL к XE-версиям Delphi."
      Спасибо. Обязательно посмотрю. А что там с 64-битами? Не знаете случайно?

      Удалить
    5. NameRec:
      «Одна ремарка. Бывает (если это описано в шаблоне генерации), что код - тоже "сам" генерируется. Тогда как раз он тегами - не окружается.»
      -- На шаблоны я обратил внимание.
      Как я понимаю, это ваш (Вашей организации) собственный DSL.
      Пока могу только догадываться, в каких случаях требуются шаблоны и чем мотивировано их появление. Вероятно, это вызвано желанием определить специальные способы трансляции отношений (зависимость, агрегирование) для разных UML-диаграм. Надеюсь, Вы найдёте время раскрыть эту тему.

      «"Да, там есть условно-поключаемая реализация хранения служебных структур в своём собственном пуле RAM"
      Знакомо :-)»
      -- Да, такое решение относительно часто встречается. Другое дело, я никогда не находил в нём особенной пользы, поскольку работать оно начинает на большом количестве данных, т.е. в случаях, когда уже уместно применить простую СУБД (например, SQLite).

      «"Мы отключаем - FastMM работает достаточно эффективно..."
      Я знаю :-) FastMM - хорош.»
      -- Он кстати, доступен со времён Delphi 7, если не ошибаюсь...

      «"Кстати, есть порт DeCAL к XE-версиям Delphi."
      Спасибо. Обязательно посмотрю. А что там с 64-битами? Не знаете случайно?»
      -- Здесь - не уверен. Хотя модуль небольшой, "хоккея" там практически нет :-) Это не какой-нибудь code injection :-)
      Я не думаю, что XE-реализацию будет сложно портировать на 64-разряда, хотя и утверждать не возьмусь, поскольку соответствующего опыта не имею...

      Удалить
    6. "Как я понимаю, это ваш (Вашей организации) собственный DSL.
      Пока могу только догадываться, в каких случаях требуются шаблоны и чем мотивировано их появление. Вероятно, это вызвано желанием определить специальные способы трансляции отношений (зависимость, агрегирование) для разных UML-диаграм. Надеюсь, Вы найдёте время раскрыть эту тему."

      Именно так. Надеюсь - найду.

      "Да, такое решение относительно часто встречается. Другое дело, я никогда не находил в нём особенной пользы, поскольку работать оно начинает на большом количестве данных, т.е. в случаях, когда уже уместно применить простую СУБД (например, SQLite)."

      У меня была реальная необходимость.

      "Он кстати, доступен со времён Delphi 7, если не ошибаюсь..."

      Тогда я его к сожалению не использовал "промышленно".

      "Здесь - не уверен. Хотя модуль небольшой, "хоккея" там практически нет :-) Это не какой-нибудь code injection :-)
      Я не думаю, что XE-реализацию будет сложно портировать на 64-разряда, хотя и утверждать не возьмусь, поскольку соответствующего опыта не имею..."

      Я вот как раз и надеюсь на "отсутствие хоккея" и что всё будет хорошо. Другое дело, что у меня СОБСТВЕННАЯ инфраструктура (контейнеров в частности) - посему не факт, что удастся быстро и безболезненно "скрестить ежа с ужом".

      Удалить
    7. "собственный DSL"

      Пока одно могу сказать, что его можно править в процессе редактирования самой прикладной модели. Через UML.

      Заводить новые стереотипы и отношения между ними. Видоизменять шаблоны генерации.

      Т.е. DSL - "растёт" вместе с прикладными проектами.

      Т.е. "по ходу пьесы" - можно выделять "шаблонные решения". Что я активно и делаю.

      Удалить
    8. "Это не какой-нибудь code injection"

      Вы про "вызов локальных функций"? :-)

      Удалить
    9. NameRec:

      «"...Надеюсь, Вы найдёте время раскрыть эту тему."
      Именно так. Надеюсь - найду.»
      -- Было бы очень интересно.
      Хотелось бы также услышать от Вас причины, приведшие к необходимости разработки собственного DSL для кодогенерации. Этот DSL показался мне излишне криптованым, понятно, что XML"многословен", но IMHO он и знаком большему количеству людей (что снижает "порог входа") и, на мой взгляд, несколько более структурирован.
      Кроме того, в случае, можно было бы попробовать некий "симбиоз" XML-описания с LUA/Python императивом.
      Возможно, это всё выглядело бы не столь кратко, но возможно, чуть более понятно.
      Хотя, говорить это меня подталкивает, скорее, личный опыт, нежели точное осознание задач, решаемых кодогенерацией.
      На эту тему хотелось бы получить Ваши комментарии.

      «"Да, такое решение относительно часто встречается. Другое дело, я никогда не находил в нём особенной пользы, поскольку работать оно начинает на большом количестве данных, т.е. в случаях, когда уже уместно применить простую СУБД (например, SQLite)."
      У меня была реальная необходимость.»
      -- Если не секрет, поделитесь хотя бы типажом задачи.
      Интересно уловить ситуации, когда потребуются, скажем, массивы (или списки) долиной порядка 100 000 элементов.

      «Другое дело, что у меня СОБСТВЕННАЯ инфраструктура (контейнеров в частности) - посему не факт, что удастся быстро и безболезненно "скрестить ежа с ужом".»
      -- Это-то как раз, понятно :-)

      «"собственный DSL"
      Пока одно могу сказать, что его можно править в процессе редактирования самой прикладной модели. Через UML.»
      -- Вы говорите о шаблонах, которые используются для кодогенерации и обеспечивают раскрытие специфики конкретной диаграммы? Иля я Вас неправильно понял?

      «Заводить новые стереотипы и отношения между ними. Видоизменять шаблоны генерации.»
      -- Тема стереотипов и их отображения в код, IMHO самая существенная часть Вашей методологии. Было бы очень интересно, если бы Вы осветили эту тему.

      «Т.е. DSL - "растёт" вместе с прикладными проектами.»
      -- Вероятно, под DSL Вы понимаете сейчас чуть ли не саму совокупность UML-диаграм, описывающих конкретный проект. Иля я понял Вас неверно?

      «Т.е. "по ходу пьесы" - можно выделять "шаблонные решения". Что я активно и делаю.»
      -- Не ошибусь ли я, если предположу, что:
      1. "Шаблонные решения" привязаны к конкретной диаграмме или, возможно, к семейству диаграмм?
      2. Необходимость в выделении "шаблонных решений" вызывается потребностью конкретизировать отношения между элементами диаграмм? Если так, то какие отношения Вы находите необходимым специализировать шаблонами?

      «"Это не какой-нибудь code injection"
      Вы про "вызов локальных функций"? :-)»
      -- Нет :-)
      Про code injection я упомянул как о некоей разновидности "шаманства", грозящей сложностями с адаптацией при смене версии Delphi или, уж тем более, разрядности платформы.
      К распространённому (в своё время) примеру такого "шаманства" я отношу реализацию итераторов в Turbo Vision, от которой нам проще было отказаться, чем адаптировать её для 32-разрядного Delphi.

      Удалить
    10. ""Шаблонные решения" привязаны к конкретной диаграмме или, возможно, к семейству диаграмм?"

      Шаблонные решения привязываются к стереотипу.

      Удалить
  2. "примеру такого "шаманства" я отношу реализацию итераторов в Turbo Vision"

    http://18delphi.blogspot.ru/2013/03/blog-post_5929.html

    ;-)

    ОтветитьУдалить
    Ответы
    1. NameRec: Разумеется, моё упоминание итераторов Turbo Vision было неслучайным ;-)

      Удалить