вторник, 14 апреля 2015 г.

Коротко. Ни о чём. Инверсия зависимостей

Занимаюсь последнее время перетряхиванием наших библиотек на предмет исправления зависимостей.

В библиотеках несколько миллионов строк кода и несколько тысяч модулей.

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

И это не "пустые разговоры" ни о чём.

У нас за долгое время работы было написано:

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

Комментариев нет:

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