1 Eylül 2012 Cumartesi

Dependency Injection Giriş

{
  Inversion of Control
  Dependency injection at Delphi for Loose Coupling
  İsmail KOCACAN 2012
}

unit uMain;

interface

uses
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  Vcl.Graphics,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Dialogs;

type
  IDocument = interface
    // GUID oluşturmak için kısayol : Ctrl+Shift+G
    ['{44E273D9-3C17-4821-B791-31CDDCE6C6D8}']
    procedure Parse;
  end;

  TBaseDocument = class abstract(TInterfacedObject)
  private
    FPath: string;
    FContent: string;
  public
    property Path: string read FPath write FPath;
    property Content: string read FContent write FContent;
  end;

  TXMLDocument = class(TBaseDocument, IDocument)
  public
    procedure Parse;
  end;

  TJSONDocument = class(TBaseDocument, IDocument)
  public
    procedure Parse;
  end;

  TDocumentParser = class
  private
    FDocument: IDocument;
  public
    constructor Create(aDocument: IDocument);
    procedure DoParse;
  end;

  TfrmMain = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

{ TXMLDocument }
procedure TXMLDocument.Parse;
begin
  OutputDebugString('XML parseleme işlemi yapıldı.');
end;

{ TJSONDocument }
procedure TJSONDocument.Parse;
begin
  OutputDebugString('JSON parseleme işlemi yapıldı.');
end;

{ TDocumentParser }
constructor TDocumentParser.Create(aDocument: IDocument);
begin
  FDocument := aDocument;
end;

procedure TDocumentParser.DoParse;
begin
  FDocument.Parse;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
var
  axmldocument: TXMLDocument;
  ajsondocument: TJSONDocument;
  aParser: TDocumentParser;
begin
  axmldocument := TXMLDocument.Create;
  aParser := TDocumentParser.Create(axmldocument);
  aParser.DoParse;

  ajsondocument := TJSONDocument.Create;
  aParser := TDocumentParser.Create(ajsondocument);
  aParser.DoParse;
end;

end.
TBaseDocument taban sınıfını, "IInterface" arayüzünün üyelerini implemente etmiş,
ve "TObject" sınıfın tüm özelliklerini bünyesinde barındıran "TInterfacedObject" sınıfından kalıtıyoruz.

"TBaseDocument" sınıfını "TInterfacedObject" sınıfından kalıtmamızın sebebi ise ;
Üst sınıflarda(TXMLDocument,TJSONDocument) "IDocument" arayüzünü implemente ederken
"IInterface" arayüzünün fonksiyonlarını da bizden implemente ettirmek istemesidir.
"TBaseDocument" sınıfından kalıtarak oluşturduğumuz her yeni sınıfın,"IInterface" arayüzünün methodlarını da, implemente ettirmek kullanışlı bir yöntem değil...

O Sebeble ; TBaseDocument taban sınıfımızı türetebileceğimiz,"IIInterface" arayüzünün fonksiyonlarını implemente etmiş,başka bir sınıf lazım.Bu ihtiyacı karşılayacak olan sınıfı kendimizde yazabilirdik fakat,
hali hazırda var olan "TInterfacedObject" ihtiyacımızı karşılıyor. "TBaseDocument" sınıfını TInterfacedObject sınıfından kalıtmamızın sebebi de budur.

"TXMLDocument" ve "TJSONDocument" sınıflarını "TBaseDocument" sınıfından türetiyoruz.
Ve "IDocument" arayüzünün methodlarını implemente ettiriyoruz.
Nitekim her yeni TBilmemneDocument nesnesinin Parse procedure'sinin gövdesinde farklı işlemler olacaktır.

Dependency injection prensibinin "Setter" procedure'ler ve Constructor methodlar ile uygulanadığını okumuştum.Tabiki bence sadece bunlarla sınırlı değil injection olayı...
Biz nesneleri dış dünyaya,o nesnenin üyeleri ile açtığımız için,bence sadece set edilebilen, bir property üzerinden de injection yapılabilir diye düşünüyorum...
injection kısmı ile daha kullanışlı yazım şekilleri aklımda var ama onlar bir daha ki sefere...

Dependency injection prensibin uygulandığı kısım ise "TDocumentParser" sınıfının yapılandırıcısında.
constructor Create(aDocument: IDocument);
Bu şekilde yazmakla derleyiciye şunu demiş oluyoruz.IDocument arayüzünde veya IDocument arayüzünü implemente etmiş bir sınıf, parametre olarak geçilebilir.Bu şekilde yazmak tipden bağımsız olduğu için, daha esnek bir kodlama oluyor.Nasıl yani? Esnek derken ?

Yani IDocument arayüzünü implemente etmiş,TBaseDocument sınıfında türeyen ,yeni yeni sınıflar yazıp,
TDocumentParser sınıfın yapılandırıcında hiçbir değişiklik yapmadan,bu yeni sınıfları parametre olarak geçebiliriz.Bu tipden bağımsız,soyutlama yöntemi ile yazım tarzıydı.


Şimdi birde soyutlamadan yazdığımızı düşünecek olursak;
constructor Create(aDocument: TXMLDocument); overload;
constructor Create(aDocument: TJSONDocument); overload;
Her yeni yazdığımız üst sınıfda(misal TBilmemneDocument) Parse methodunu çağırabilmemiz için,
constructor Create(aDocument: TBilmemneDocument); overload; gibi değişikliği veya başka bir yazım tarzını, "TDocumentParser" sınıfında yapmamız gerekecekti.


Rtti ve Dependency Injection ile ilgili aklımda bir senaryo da var.
Bir daha ki sefere...


Kaynak kodları(XE2) buradan indirebilirsiniz

Kaynaklar : 
http://martinfowler.com/articles/injection.html
http://www.kodcu.com/2011/04/inversion-of-control-ioc-nedir/