Занимаюсь последнее время перетряхиванием наших библиотек на предмет исправления зависимостей.
В библиотеках несколько миллионов строк кода и несколько тысяч модулей.
Библиотеки используются в нескольких десятках программных проектов. Над которыми трудится достаточно большой коллектив программистов.
И это не "пустые разговоры" ни о чём.
У нас за долгое время работы было написано:
1. Собственный текстовый редактор на уровне Word 97.
2. Собственная реализация IStorage. Эффективнее, чем у MS.
3. Собственный полнотекстовый индексатор.
4. Расширения к DUnit.
5. Собственная скриптовая машина (FORTH-like) на уровне Python.
6. Собственная реализация тестирования GUI-сценариев - на "естественном языке".
7. Собственный рендеринг RTF-like документов для iOS.
8. Собственная реализация SAX и DOM парадигм.
9. Собственная кодогенерация из UML.
10. Собственная реализация MVC-like фреймворка.
Зависимости показывает лишь рисование всего этого хозяйства на UML с последующей валидацией связей и циклов. А также кодогенерацией.
И уже не раз я применил нехитрый приём "инверсии зависимостей".
Который мне крайне нравится.
И я настолько вошёл во вкус, что даже сделал отдельный шаблон кодогенерации всего этого хозяйства.
Приведу пример. БЕЗ шаблона кодогенерации. В БАЗОВЫХ примитивах.
Выглядит примерно так:
Было:
A call B.method
A было связано с B,
стало:
A call C.method
B implements C.method
A связано с C.
B связано с C.
A про B теперь ничего не знает.
B про A - тоже ничего не знает.
A знает про C.
B знает про C.
С выполняет роль "сервиса" с возможностью подмены поведения.
Диаграмма:
Код:
Точка инъекции:
Собственно инъекция:
И вообще говоря подобную технику применяет Embarcadero в FMX.
У них это называется сервисы (Services).
Ну с полноценным Service-Locator'ом.
Ну а в НЕБАЗОВЫХ примитивах это выглядит ПРОЩЕ. Но об этом я напишу в следующий раз. Если будет интерес.
Для любителей "придираться к пробелам и запятым" - ПОДЧЕРКНУ, что это всего лишь - ИДЕЯ, а не руководство к действию.
Ну и понятное дело - ссылку на Spring For Delphi - я уже приводил.
И ещё коллега писал на ту же тему - Коллега написал. Пример Dependency Injection.
И ещё:
Ссылка. Критический взгляд на принцип инверсии зависимостей
Ссылка. Принцип инверсии зависимости
И сходная тема была поднята тут - О тестах и специально оборудованных "контрольных точках"
P.S. Мы за последние несколько недель сделали несколько десятков сервисов. И очень сильно развязали зависимости между библиотеками.
P.P.S. Продолжение - http://programmingmindstream.blogspot.ru/2015/03/blog-post_73.html
В библиотеках несколько миллионов строк кода и несколько тысяч модулей.
Библиотеки используются в нескольких десятках программных проектов. Над которыми трудится достаточно большой коллектив программистов.
И это не "пустые разговоры" ни о чём.
У нас за долгое время работы было написано:
1. Собственный текстовый редактор на уровне Word 97.
2. Собственная реализация IStorage. Эффективнее, чем у MS.
3. Собственный полнотекстовый индексатор.
4. Расширения к DUnit.
5. Собственная скриптовая машина (FORTH-like) на уровне Python.
6. Собственная реализация тестирования GUI-сценариев - на "естественном языке".
7. Собственный рендеринг RTF-like документов для iOS.
8. Собственная реализация SAX и DOM парадигм.
9. Собственная кодогенерация из UML.
10. Собственная реализация MVC-like фреймворка.
Зависимости показывает лишь рисование всего этого хозяйства на UML с последующей валидацией связей и циклов. А также кодогенерацией.
И уже не раз я применил нехитрый приём "инверсии зависимостей".
Который мне крайне нравится.
И я настолько вошёл во вкус, что даже сделал отдельный шаблон кодогенерации всего этого хозяйства.
Приведу пример. БЕЗ шаблона кодогенерации. В БАЗОВЫХ примитивах.
Выглядит примерно так:
Было:
A call B.method
A было связано с B,
стало:
A call C.method
B implements C.method
A связано с C.
B связано с C.
A про B теперь ничего не знает.
B про A - тоже ничего не знает.
A знает про C.
B знает про C.
С выполняет роль "сервиса" с возможностью подмены поведения.
Диаграмма:
Код:
Точка инъекции:
unit l3DispatcherHelper; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Библиотека "L3$AFW" // Модуль: "w:/common/components/rtl/Garant/L3/l3DispatcherHelper.pas" // Родные Delphi интерфейсы (.pas) // Generated from UML model, root element: SimpleClass::Class Shared Delphi Low Level::L3$AFW::VCMHelpers::Tl3DispatcherHelper // // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // ! Полностью генерируется с модели. Править руками - нельзя. ! {$Include ..\L3\l3Define.inc} interface uses l3ProtoObject ; (* Ml3DispatcherHelper = PureMixIn procedure ClearHistory; end;//Ml3DispatcherHelper *) type Il3DispatcherHelper = interface(IUnknown) ['{41B8F325-9AFB-447E-B3E7-2C433912BC2A}'] // Ml3DispatcherHelper procedure ClearHistory; end;//Il3DispatcherHelper Tl3DispatcherHelper = class(Tl3ProtoObject) private // private fields f_Alien : Il3DispatcherHelper; public // realized methods procedure ClearHistory; protected // overridden protected methods procedure ClearFields; override; {* Сигнатура метода ClearFields } public // public methods procedure SetAlienHelper(const anAlien: Il3DispatcherHelper); class function Exists: Boolean; {* Проверяет создан экземпляр синглетона или нет } public // singleton factory method class function Instance: Tl3DispatcherHelper; {- возвращает экземпляр синглетона. } end;//Tl3DispatcherHelper implementation uses l3Base {a} ; // start class Tl3DispatcherHelper var g_Tl3DispatcherHelper : Tl3DispatcherHelper = nil; procedure Tl3DispatcherHelperFree; begin l3Free(g_Tl3DispatcherHelper); end; class function Tl3DispatcherHelper.Instance: Tl3DispatcherHelper; begin if (g_Tl3DispatcherHelper = nil) then begin l3System.AddExitProc(Tl3DispatcherHelperFree); g_Tl3DispatcherHelper := Create; end; Result := g_Tl3DispatcherHelper; end; procedure Tl3DispatcherHelper.SetAlienHelper(const anAlien: Il3DispatcherHelper); //#UC START# *5501A41602E7_5501A3AE02AA_var* //#UC END# *5501A41602E7_5501A3AE02AA_var* begin //#UC START# *5501A41602E7_5501A3AE02AA_impl* Assert(f_Alien = nil); f_Alien := anAlien; //#UC END# *5501A41602E7_5501A3AE02AA_impl* end;//Tl3DispatcherHelper.SetAlienHelper class function Tl3DispatcherHelper.Exists: Boolean; {-} begin Result := g_Tl3DispatcherHelper <> nil; end;//Tl3DispatcherHelper.Exists procedure Tl3DispatcherHelper.ClearHistory; //#UC START# *5501A435019E_5501A3AE02AA_var* //#UC END# *5501A435019E_5501A3AE02AA_var* begin //#UC START# *5501A435019E_5501A3AE02AA_impl* if (f_Alien <> nil) then f_Alien.ClearHistory; //#UC END# *5501A435019E_5501A3AE02AA_impl* end;//Tl3DispatcherHelper.ClearHistory procedure Tl3DispatcherHelper.ClearFields; {-} begin f_Alien := nil; inherited; end;//Tl3DispatcherHelper.ClearFields end.
Собственно инъекция:
TvcmDispatcherHelper = class(Tl3ProtoObject, Il3DispatcherHelper) public // realized methods procedure ClearHistory; public // public methods class function Exists: Boolean; {* Проверяет создан экземпляр синглетона или нет } public // singleton factory method class function Instance: TvcmDispatcherHelper; {- возвращает экземпляр синглетона. } end;//TvcmDispatcherHelper ... // start class TvcmDispatcherHelper var g_TvcmDispatcherHelper : TvcmDispatcherHelper = nil; procedure TvcmDispatcherHelperFree; begin l3Free(g_TvcmDispatcherHelper); end; class function TvcmDispatcherHelper.Instance: TvcmDispatcherHelper; begin if (g_TvcmDispatcherHelper = nil) then begin l3System.AddExitProc(TvcmDispatcherHelperFree); g_TvcmDispatcherHelper := Create; end; Result := g_TvcmDispatcherHelper; end; class function TvcmDispatcherHelper.Exists: Boolean; {-} begin Result := g_TvcmDispatcherHelper <> nil; end;//TvcmDispatcherHelper.Exists procedure TvcmDispatcherHelper.ClearHistory; //#UC START# *5501A435019E_5501A60D002E_var* //#UC END# *5501A435019E_5501A60D002E_var* begin //#UC START# *5501A435019E_5501A60D002E_impl* if (vcmDispatcher <> nil) then if (vcmDispatcher.History <> nil) then vcmDispatcher.History.Clear(false); //#UC END# *5501A435019E_5501A60D002E_impl* end;//TvcmDispatcherHelper.ClearHistory ... Tl3DispatcherHelper.Instance.SetAlienHelper(TvcmDispatcherHelper.Instance); ...
И вообще говоря подобную технику применяет Embarcadero в FMX.
У них это называется сервисы (Services).
Ну с полноценным Service-Locator'ом.
Ну а в НЕБАЗОВЫХ примитивах это выглядит ПРОЩЕ. Но об этом я напишу в следующий раз. Если будет интерес.
Для любителей "придираться к пробелам и запятым" - ПОДЧЕРКНУ, что это всего лишь - ИДЕЯ, а не руководство к действию.
Ну и понятное дело - ссылку на Spring For Delphi - я уже приводил.
И ещё коллега писал на ту же тему - Коллега написал. Пример Dependency Injection.
И ещё:
Ссылка. Критический взгляд на принцип инверсии зависимостей
Ссылка. Принцип инверсии зависимости
И сходная тема была поднята тут - О тестах и специально оборудованных "контрольных точках"
P.S. Мы за последние несколько недель сделали несколько десятков сервисов. И очень сильно развязали зависимости между библиотеками.
P.P.S. Продолжение - http://programmingmindstream.blogspot.ru/2015/03/blog-post_73.html
Комментариев нет:
Отправить комментарий