среда, 15 апреля 2015 г.

Briefly. Shooting the breeze. Dependency inversion

Original in Russian: http://programmingmindstream.blogspot.ru/2015/03/blog-post_12.html

Lately I’ve been shaking up our libraries for correcting of dependences.

The libraries contain several millions of code lines and several thousands of modules.

They are used in several tens of programming projects developed by fairly large number of programmers teams.

That is not an “idle talk”.

For long time we have written:

1. Our own word processing application on a level of Word 97.
2. Our own implementation of IStorage, which is more effective than from MS.
3. Our own full-text indexer.
4. DUnit add-ins (in Russian).
5. Our own script engine (FORTH-like) on the level of Python.
6. Our own implementation of GUI-cases testing in “natural language”.
7. Our own rendering of RTF-like documents for iOS.
8. Our own implementation of SAX and DOM paradigms.
9. Our own code generation from UML.
10. Our own implementation of MVC-like framework.

The dependences are shown only by drawing of this staff on UML and, subsequently, validation of relations and cycles as well as code generation.

I’ve already used the simple “dependency inversion” more than once.

I really like it.

I developed such a liking for it that I even made a separate code generation pattern for this staff.

I’ll give an example – WITHOUT code generation pattern, in BASE primitives.

It looks something like this:

Previously:

A call B.method

A was connected to B,

Now:

A call C.method
B implements C.method

A is connected to C.
B is connected to C.

A has “no idea” of B.
B has “no idea” of A, too.

A knows about C.
B knows about C.

С is a “service” enabling behavior substitution.

The diagram:



The code:

Injection point:
unit l3DispatcherHelper;
 
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// The library "L3$AFW"
// The unit: "w:/common/components/rtl/Garant/L3/l3DispatcherHelper.pas"
// Native Delphi interfaces (.pas)
// Generated from UML model, root element: SimpleClass::Class Shared Delphi Low Level::L3$AFW::VCMHelpers::Tl3DispatcherHelper
//
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
// ! Completely generated from the model. It is not allowed to edit manually. !
 
{$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;
     {* Signature of ClearFields method }
 public
 // public methods
   procedure SetAlienHelper(const anAlien: Il3DispatcherHelper);
   class function Exists: Boolean;
     {* We check if singleton instance has been created }
 public
 // singleton factory method
   class function Instance: Tl3DispatcherHelper;
    {- returns the singleton instance. }
 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.

The injection:
TvcmDispatcherHelper = class(Tl3ProtoObject, Il3DispatcherHelper)
 public
 // realized methods
   procedure ClearHistory;
 public
 // public methods
   class function Exists: Boolean;
     {* We check if singleton instant has been created }
 public
 // singleton factory method
   class function Instance: TvcmDispatcherHelper;
    {- returns the singleton instance. }
 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);
...

Actually, this approach is used by Embarcadero in FMX.

They call it Services.

Sure, with a true Service-Locator.

It is more SIMPLE in NON-BASE primitives. But I’ll write about it edit next time, if you are interested.

For those who like to “nit-pick through spaces and commas” – I’ll STRESS it is only an IDEA, not a guide to action.

Naturally, I’ve already given a link to Spring For Delphi.

My colleague also wrote about it – The colleague wrote. Example of Dependency Injection  (in Russian).

Some more (in Russian):

Link. Taking stock of dependency inversion principle
Link. Dependency inversion principle 

A similar problem was discussed here – About tests and specially-fitted “check points”


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

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