21 Aralık 2023 Perşembe

Örnek DI uygulaması 6 - Displayer'leri kaldırıp bir adet generic displayer oluşturmak.

 Kod kalitemizi bir adım daha ilerleteceğiz. Bu küçük ama projeyi çok sadeleştirecek bir adım olacak. Gördüğümüz gibi her tablomuz için bir TFrame oluşturuyoruz, ancak her TFrame için de onu içinde barındıracak bir zarf oluşturmak zorunda kalıyoruz. Peki bunu bir tane yapıp parametrelerini değiştirerek çalışır hale getirebilir miyiz?

Bu yazının konusu da bu işi yapmak olacak!

Öncelikle View.MyBaseFrame.pas unit'inde şu eklemeyi yapıyoruz. Bu bize daha sonra oluşturacağımız Displpayer'da yardımcı olacak bir tanımlama:

...
  TframeMyBaseClass = class of TframeMyBase;
...
Sonrasında uEntityDisplayer.pas adlı bir unit ekleyelim projeye. İçinde şunlar olacak
unit uEntityDisplayer;

interface
uses System.Classes, uInterfaces, Vcl.Controls, Vcl.ExtCtrls, Vcl.ComCtrls, Vcl.Forms, View.MyBaseFrame;

type
  TEntityDisplayer<T : TframeMyBase, constructor> = class(TInterfacedObject, IDisplayOnTabSheetGeneric)
  public
    procedure DisplayOnTabSheet(const aTabSheet: TTabSheet);
  end;

implementation

{ TEntityDisplayer }

procedure TEntityDisplayer<T>.DisplayOnTabSheet(const aTabSheet: TTabSheet);
var frm : TFrame;
begin
  frm := T(TframeMyBaseClass(T).Create(Application.MainForm));
  frm.Parent := aTabSheet;
  frm.Align := alClient;
  aTabSheet.Caption := (frm as T).GetCaption;
end;

end.
Diğer 3 adet displayer projeden çıkarılacak ve dosyalar diskten silinecek. Yanlışlıkla onların işlem görmesini istemeyiz.

Son olarak ta uRegistration değiştirilecek

unit uRegistration;

interface

uses
  Spring.Container, uInterfaces;

procedure RegisterInterfaces(aMyConnection : IMyConnection);

implementation
uses
    uEntityDisplayer
   ,View.Inventories
   ,View.Customers
   ,View.Suppliers
    ;

procedure RegisterInterfaces(aMyConnection : IMyConnection);
begin
  GlobalContainer.RegisterType<IDisplayOnTabSheet, TEntityDisplayer<TframeInventories>>('inventories');
  GlobalContainer.RegisterType<IDisplayOnTabSheet, TEntityDisplayer<TframeCustomers>>('customers');
  GlobalContainer.RegisterType<IDisplayOnTabSheet, TEntityDisplayer<TframeSuppliers>>('suppliers');


  GlobalContainer.RegisterInstance<IMyConnection>(aMyConnection, 'connection');

  GlobalContainer.Build;
end;

end.
Evet bundan sonra F9 yaptığımızda uygulamamız çalışacaktır.

Böylece her ekran için ayrı bir unit ve class oluşturma işinden sıyırmış olduk. Bu bizim daha sonra ekleyeceğimiz ekranlara ilişkin işlemleri hiç değiştirmeye gerek kalmadan yaptığımız bir sadeleştirme oldu.


Projenin bu şeklini şu linkten elde edebilirsiniz : DITest-5
























































Örnek DI uygulaması 5 - Frame'lerin bir PageControl üzerinde çoklu gösterimi

 Önceki yazıdaki son durum üzerine şimdi ana ekrandaki sağ kısımda yer alan pnlForClientFrames adlı TPanel bileşenini kaldırıp yerine bir TPageControl bileşeni koyacağız. İsterseniz de TMoPageControl adlı benim üzerinde bir küçük ekleme yaptığım bileşeni kullanabilirsiniz. Bunun ek özelliği her sayfayı kapatmak için, üstteki tab başlığının sağında bir kapat düğmesi olmasıdır (x). Bu düğmenin bazı sayfaları kapatması engellenebilir. Koyduğumuz bu bileşenin adına pagesForFrames diyeceğim ben.

Yine formun üzerine bir TSplitter koyalım ve bunun Align=alLeft yapalım ardından pagesForFrames için Align=alClient yapalım.

Böylelikle şimdilik ana forma amacımıza uygun şekli vermiş oluyoruz. 

uInterfaces.pas kodunu açıp orada IDisplayOnPanel'i değiştirelim.

  IDisplayOnTabSheet = interface
    ['{B28B9829-CBF3-4629-9C3F-E3859497A185}']
    procedure DisplayOnTabSeet(const aTabSheet: TTabSheet);
  end;
Sonra 3 adet displayer'i buna uygun olarak değiştireceğiz.
unit uCustomersDisplayer;

interface
uses System.Classes, uInterfaces, Vcl.Controls, Vcl.ExtCtrls, View.Customers, Vcl.ComCtrls;

type
  TCustomersDisplayer = class(TInterfacedObject, IDisplayOnTabSheet)
  public
    procedure DisplayOnTabSheet(const aTabSheet: TTabSheet);
  end;

implementation

{ TCustomersDisplayer }

procedure TCustomersDisplayer.DisplayOnTabSheet(const aTabSheet: TTabSheet);
var frm : TframeCustomers;
begin
  frm := TframeCustomers.Create(aTabSheet);
  frm.Parent := aTabSheet;
  frm.Align := alClient;
end;

end.
Diğer 2 unit de buna benzer şekilde değiştirilecek.
Daha sonra sıra uRegistration.pas'a geliyor. Ondaki kayıt cümleciklerini de şu şekilde değiştireceğiz.
unit uRegistration;

interface

uses
  Spring.Container, uInterfaces;

procedure RegisterInterfaces(aMyConnection : IMyConnection);

implementation
uses
      uInventoriesDisplayer
    , uCustomersDisplayer
    , uSuppliersDisplayer
    ;

procedure RegisterInterfaces(aMyConnection : IMyConnection);
begin
  GlobalContainer.RegisterType<IDisplayOnTabSheet, TInventoriesDisplayer>('inventories');
  GlobalContainer.RegisterType<IDisplayOnTabSheet, TCustomersDisplayer>('customers');
  GlobalContainer.RegisterType<IDisplayOnTabSheet, TSuppliersDisplayer>('suppliers');


  GlobalContainer.RegisterInstance<IMyConnection>(aMyConnection, 'connection');

  GlobalContainer.Build;
end;

end.
Ve son olarak da View.MainForm'da değişiklik yapacağız. Oradaki IDisplayOnPanel'i IDisplayOnTabSheet olarak değiştireceğiz. Ve elbette bir iki ek daha olacak. Önce ats adlı TabSheet yaratıyoruz ve bunun PageControl'ü olarak pagesForFrames'i atıyoruz. ve DisplayOnTabSheet'e parametre olarak veriyoruz, son olarak ta PageControl'ün aktif sayfası haline getiriyoruz. Yani son
bastığımız sayfa öne çıkıyor.
procedure TMainForm.ViewClientFrame(const aFrameName: string);
var
  iScr : IDisplayOnTabSheet;
  ats : TTabSheet;
begin
  ats := TTabSheet.Create(pagesForFrames);
  ats.PageControl := pagesForFrames;

  iScr := GlobalContainer.Resolve<IDisplayOnTabSheet>(aFrameName);
  iScr.DisplayOnTabSheet(ats);
  pagesForFrames.ActivePage := ats;
end;

Ama buradaki ekran görüntüsünde de gördüğümüz gibi tabların başlıklarında hiçbir şey yazmıyor, çünkü biz bir değer atamadık. Şimdi onu da yapalım.
Öncelikle ViewMyBaseFrame'deki TframeMyBase'e bir virtual, abstract metot ekleyeceğiz.
...
...
    function GetCaption : string; virtual; abstract;
  end;
...
Amacımız bundan türetilen ekranlarda, bu metodun override edilmesini (ezilmesini) zorunlu kılarak her ekrana bir ekran adı verilmesini sağlamaktır. Bundan türeyen tüm ekranlar create edildiğinde eğer bu metodu override etmemişlerse program Abstract error verecektir ve açılmayacaktır.
unit View.Customers;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, View.MyBaseFrame, Data.DB, Vcl.Grids,
  Vcl.DBGrids, Vcl.ExtCtrls;

type
  TframeCustomers = class(TframeMyBase)
  private
    { Private declarations }
  protected
    function GetSqlClause : string; override;
  public
    function GetCaption : string; override;
  end;

var
  frameCustomers: TframeCustomers;

implementation

{$R *.dfm}

{ TframeCustomers }

function TframeCustomers.GetCaption: string;
begin
  Result := 'Müşteriler';
end;

function TframeCustomers.GetSqlClause: string;
begin
  Result := 'select * from Customer order by CustomerId';
end;

end.
Diğer ekranlarımız (View.Inventories, View.Suppliers) için de ayrı ayrı yapacağız aynı işi. 
Sonrasında tekrar Displayer'lerde değişiklik yapmamız gerekiyor.

...
procedure TCustomersDisplayer.DisplayOnTabSheet(const aTabSheet: TTabSheet);
var frm : TframeCustomers;
begin
  frm := TframeCustomers.Create(aTabSheet);
  frm.Parent := aTabSheet;
  frm.Align := alClient;
  aTabSheet.Caption := frm.GetCaption;
end;
Bu değişiklik diğer iki displayer'de de yapılmalı (uInventoriesDisplpayer.pas ve uSuppliersDisplayer.pas). 


Burada da görüldüğü gibi artık tab başlıklarını da yazıyor. 

Projenin bu şeklini şu linkten elde edebilirsiniz : DITest-4




































14 Aralık 2023 Perşembe

Örnek DI uygulaması 4-Frame'leri standart bir frame'den türetmek

 Bu zamana kadar gördüğümüz gibi, her frame'de aynı işlemleri yapıyoruz. Bunları tek bir ancestor (ata) TFrame'de yapsak ve diğer tüm frame'leri ondan türetsek nasıl olur;

Önce View.Inventories ve View.Customers unit'lerini projeden çıkaralım. Bunları ve dfm dosyalarını başka bir klasöre taşıyalım.

Daha sonra yeni bir TFrame unit'i ekleyelim ve bunu View.MyBaseFrame.pas adıyla kaydedelim. İçindeki frame'e frameMyBase adı verelim, üzerine 2 adet panel koyalım. Panel2'nin Align=alBottom ve Panel1'in Align=alClient yapalım. Her ikisinin de BevelOuter=bvNone ve Caption={boş} yapalım.

Panel1 üzerine bir DBGrid ve bir de DataSource koyalım. DBGrid'in Align=alClient, DataSource=DataSource1 yapalım. Ayrıca DBGrid'in ReadOnly=True, Options.RowSelect=True yapalım.

TframeMyBase şu şekilde olsun,

unit View.MyBaseFrame;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Data.DB,
  Vcl.Grids, Vcl.DBGrids,
  uInterfaces,
  Spring.Container;

type
  TframeMyBase = class(TFrame)
    Panel2: TPanel;
    Panel1: TPanel;
    DBGrid1: TDBGrid;
    DataSource1: TDataSource;
  private
  protected
    function GetSqlClause : string; virtual; abstract;
  public
    constructor Create(AOwner: TComponent);
    { Public declarations }
  end;

implementation

{$R *.dfm}

constructor TframeMyBase.Create(AOwner: TComponent);
var
  aConnection: IMyConnection;
begin
  inherited;
  aConnection := GlobalContainer.Resolve<IMyConnection>('connection', []);
  DataSource1.DataSet := aConnection.GetQuery(GetSqlClause);
end;

end.
Burada görüleceği üzere GetSqlClause adlı bir abstract fonksiyon tanımlandı. Bundan amaç buna burada bir değer vermeyeceğiz ancak bundan türetilen Frame'ler bu fonksiyonu ezerek değer atamak zorunda kalacaklardır. Bu şekilde sonuç View.Inventories ya da View.Customers gibi birimlerimizin her biri için ayrı ayrı hep aynı işlemleri yapmaktan kurtulmuş olacağız. Ayrıca Create yapılandırıcısını her biri için yazmamız da gerekmeyecek.

Şimdi ikinci aşama olarak File/New/Other/Inheritable Items/frameMyBase seçeceğiz ve yaptığımız bu Base frame'den yeni View'larımızı türeteceğiz. 
İlkini View.Inventories.pas adıyla save edelim ve frame adına frameInventories yazalım. Burada gördüğümüz gibi bu frame için Panel, DBGrid ve DataSource hazır geldi zaten karşımıza.
Bunda protected kısmında GetSqlClause fonksiyonunu yazmamız gerekiyor onu da şöyle yapalım:
unit View.Inventories;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, View.MyBaseFrame, Data.DB, Vcl.Grids,
  Vcl.DBGrids, Vcl.ExtCtrls;

type
  TframeInventories = class(TframeMyBase)
  private
    { Private declarations }
  protected
    function GetSqlClause : string; override;
  public
    { Public declarations }
  end;

var
  frameInventories: TframeInventories;

implementation

{$R *.dfm}

{ TframeInventories }

function TframeInventories.GetSqlClause: string;
begin
  Result := 'select * from InventoryItem order by InventoryItemId';
end;

end.
View.MyBaseFrame'den bir frame daha inherit edelim ve bunu da View.Customers olarak kaydedelim, frame adını frameCustomers yapalım. F9'a bastığımızda aynen çalışacaktır. Çünkü bunlar için diğer bağlantı kodlarını oluşturmuştuk.

Şimdi, daha önce oluşturmadığımız tedarikçiler ekranını ekleyelim projeye. Bu şekilde yeni bir view eklediğimizde ne yapmamız gerektiğini de gözden geçirmiş olalım.
İlk olarak biraz yukarıda anlatıldığı gibi, File/New/Other/Inheritable Items/frameMyBase seçerek türetilmiş frame'i oluşturalım. Bunu View.Suppliers.pas adıyla kaydedelim, frame'i frameSuppliers olarak adlandıralım ve
unit View.Suppliers;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, View.MyBaseFrame, Data.DB, Vcl.Grids,
  Vcl.DBGrids, Vcl.ExtCtrls;

type
  TframeSuppliers = class(TframeMyBase)
  private
    { Private declarations }
  protected
    function GetSqlClause : string; override;
  public
    { Public declarations }
  end;

var
  frameSuppliers: TframeSuppliers;

implementation

{$R *.dfm}

{ TframeSuppliers }

function TframeSuppliers.GetSqlClause: string;
begin
  Result := 'select * from Supplier order by SupplierId';
end;

end.
Daha sonra bunu konteyner'le bağlantısını kurmak üzere uSuppliersDisplayer.pas'ı oluşturalım

unit uSuppliersDisplayer;

interface
uses System.Classes, uInterfaces, Vcl.Controls, Vcl.ExtCtrls, View.Suppliers;

type
  TInventoriesDisplayer = class(TInterfacedObject, IDisplayOnPanel)
  public
    procedure DisplayOnPanel(const aPanel: TPanel);
  end;

implementation

{ TSuppliersDisplayer }

procedure TInventoriesDisplayer.DisplayOnPanel(const aPanel: TPanel);
var frm : TframeSuppliers;
begin
  frm := TframeSuppliers.Create(aPanel);
  frm.Parent := aPanel;
  frm.Align := alClient;
end;

end.

Üçüncü olarak uRegistration.pas'a bunu ekleyeceğiz.

unit uRegistration;

interface

uses
  Spring.Container, uInterfaces;

procedure RegisterInterfaces(aMyConnection : IMyConnection);

implementation
uses
      uInventoriesDisplayer
    , uCustomersDisplayer
    , uSuppliersDisplayer
    ;

procedure RegisterInterfaces(aMyConnection : IMyConnection);
begin
  GlobalContainer.RegisterType<IDisplayOnPanel, TInventoriesDisplayer>('inventories');
  GlobalContainer.RegisterType<IDisplayOnPanel, TCustomersDisplayer>('customers');
  GlobalContainer.RegisterType<IDisplayOnPanel, TSuppliersDisplayer>('suppliers');


  GlobalContainer.RegisterInstance<IMyConnection>(aMyConnection, 'connection');

  GlobalContainer.Build;
end;

end.
ve son olarak da, MainForm'da Tedarikçiler düğmesinin OnClick event'ini yazacağız.
procedure TMainForm.btnTedarikcilerClick(Sender: TObject);
begin
  ViewClientFrame('suppliers');
end;
Evet, her yeni view eklemede ne yapacağımızı standardize etmiş olduk artık. 
Şimdi sırada bunu daha da iyileştirebilir miyiz onun araştırması olacak! Ve ardından bu view'lara bağlı olarak insert-update-delete-view detail ekranlarının sisteme entegre edilmesi söz konusu edilecek.

Projenin bu şeklini şu linkten elde edebilirsiniz : DITest-3





Örnek DI uygulaması 3 - Projeye Müşterileri gösterecek yeni frame eklemek

 Bu aşamada her modül için bir uXxxDisplayer.pas ve bir View.Xxx.pas unit'lerini oluşturacağız. 

Önce View.Customers.pas'ı oluşturalım. Boş bir VCL Frame oluşturalım. Bunun üzerine 2 adet TPanel koyalım, Panel2'nin Align özelliği alBottom, Caption boş, Panel1'in Align özelliği alClient Caption boş olsun. Panel1 üzerine bir DBGrid koyalım, özelliklerini şu şekilde değiştirelim;

Align=alClient 

DataSource=DataSource1

ReadOnly=True

Options.dgEditing=False

Options.RowSelect=True

Frame adını TframeCustomers yapalım.

Create yapılandırıcısını ekleyelim

constructor TframeCustomers.Create(AOwner: TComponent);
var
  aConnection: IMyConnection;
begin
  inherited;
  aConnection := GlobalContainer.Resolve<IMyConnection>('connection', []);
  DataSource1.DataSet := aConnection.GetQuery('select * from Customer order by CustomerId');
end;

Şimdi uCustomerDisplayer.pas'ı oluşturalım.
unit uCustomersDisplayer;


interface
uses System.Classes, uInterfaces, Vcl.Controls, Vcl.ExtCtrls, View.Customers;

type
  TCustomersDisplayer = class(TInterfacedObject, IDisplayOnPanel)
  public
    procedure DisplayOnPanel(const aPanel: TPanel);
  end;

implementation

{ TInventoriesDisplayer }

procedure TCustomersDisplayer.DisplayOnPanel(const aPanel: TPanel);
var frm : TframeCustomers;
begin
  frm := TframeCustomers.Create(aPanel);
  frm.Parent := aPanel;
  frm.Align := alClient;
end;

end.
Son olarak uRegistration.pas ve View.MainForm.pas'ta eklemeler yapmamız gerekli.
uRegistration.pas şu şekli alacak.
unit uRegistration;

interface

uses
  Spring.Container, uInterfaces;

procedure RegisterInterfaces(aMyConnection : IMyConnection);

implementation
uses
      uInventoriesDisplayer
    , uCustomersDisplayer
    ;

procedure RegisterInterfaces(aMyConnection : IMyConnection);
begin
  GlobalContainer.RegisterType<IDisplayOnPanel, TInventoriesDisplayer>('inventories');
  GlobalContainer.RegisterType<IDisplayOnPanel, TCustomersDisplayer>('customers');

  GlobalContainer.RegisterInstance<IMyConnection>(aMyConnection, 'connection');

  GlobalContainer.Build;
end;

end.

MainForm'da Müşteriler butonunun OnClick event'ine şunları ekleyeceğiz. 

Bu işlemlerden sonra artık Müşteriler butonuna tıkladığımızda veritabanındaki Customer tablosunu View.Customers.pas'ta frameCustomers üzerindeki dbgridde görüntülenecektir.
Bu şeklinin kodlarını şu linkten indirebilirsiniz : DITest-2.rar













Örnek DI uygulaması 2-Projenin ve ana bağlantıların oluşturulması

Bu örneği çalıştırabilmek için MS SQL ve Delphi 10 ya da üzeri sürümler kullanmanız gerekmektedir. 

Öncelikle Delphi'de File - New - Windows VCL Application Delphi 'ye tıklayın. Bunun sonucunda yeni bir VCL uygulaması açılacaktır. Bunu DITest adlı bir klasöre yine aynı adlı proje olarak kaydediyoruz. Form'a View.MainForm adını vererek kaydediyoruz. 

Formun üzerine Tool Palette'den Toolbar, StatusBar ve 2 adet TPanel bırakıyoruz. Bunlardan bir panelin Alignment'ı taLeft yapıyoruz, sonra Tool Palette'den bir TSplitter bırakıyoruz formun üzerine ve bunun Alignment'ını da taLeft yapıyoruz. 

2. panelin Alignment'ını taClient yapıyoruz. 

Her iki panelin de Caption özelliğini boş yapıyoruz, yani yazan Panel1 ve Panel2'yi siliyoruz. Ayrıca ben BevelOuter özelliğini de None yapıyorum. 

Soldaki panel üzerine ToolPalette'den TCategoryPanelGroup bırakıyorum. Bunun üzerinde sağ klik yapıp New Panel'e basarak yeni grupçuklar yaratıyorum. Bunları da adlandırıyorum:

  • Stok İşlemleri
  • Alım Siparişleri
  • Alım İşlemleri
  • Satış Siparişleri
  • Satış İşlemleri
Elbette bunlar sadece örnek olarak varlar. Bunların üzerine pek çok madde eklenebilir.
Bu kategori panellerinin üzerinde Add Control/Button seçerek 4'er adet buton ekleyelim ve her birine ad verelim.

MainForm

Şimdi aradaki bağlantıları sağlayan kısımları oluşturmadan önce Frame'leri oluşturalım. Frame'i doğrudan Container'e bağlamayacağımız için onu başka bir sınıfla sarmalamalıyız. Bu sınıflar da her işlem ekranı için bir adet olmalı. Bunlar için öncelikle interface'leri oluşturalım.

unit uInterfaces;

interface
uses Vcl.ExtCtrls;

type
   IDisplayOnPanel = interface
    ['{F2D7E53B-5EB3-4A8F-A12D-E9C9A3F56874}']
    procedure DisplayOnPanel(const aPanel: TPanel);

  end;

implementation

end
Tabloları ekranda gösterecek tüm frame'ler bundan türetilmiş sınıfların içinde yer alacaklar. Bunun ardından stokları göstereceğimiz frame'i oluşturalım.

Önce projeye yeni bir VCL frame ekleyelim, üzerine 2 adet panel koyalım, Panel2'nin Align'ını alBottom ve Panel1'inkini alClient yapalım. Caption'larını kaldıralım, BevelOuter'larını bvNone yapalım.

Panel1'in üzerine bir DBGrid koyalım ve bunun bazı özelliklerini şu şekilde değiştirelim;
-ReadOnly=True
-Align=alClient
-Options.dgEditing=False
-Options.dgRowSelect=True

Frame'e bir TDataSource ekleyelim ve bunu DBGrid'in DataSource özelliğine bağlayalım. Yani
DBGrid1.DataSource := DataSource1;
Sonrasında DBGrid1 üzerine elle stok tablosundaki bazı alanları ekliyoruz (InventoryItemId, ItemCode, ItemName, SpecCode) ve bunların kolon genişliklerini ayarlıyoruz.

Stok liste frame'i


Şimdi bu frame'i sarmak için stok Displayer'ini oluşturalım:
unit uInventoriesDisplayer;

interface
uses System.Classes, uInterfaces, Vcl.Controls, Vcl.ExtCtrls;

type
  TInventoriesDisplayer = class(TInterfacedObject, IDisplayOnPanel)
  public
    procedure DisplayOnPanel(const aPanel: TPanel);
  end;

implementation

{ TInventoriesDisplayer }

procedure TInventoriesDisplayer.DisplayOnPanel(const aPanel: TPanel);
var frm : TframeInventories;
begin
  frm := TframeInventories.Create(aPanel);
  frm.Parent := aPanel;
  frm.Align := alClient;
end;

end.

Burada görüldüğü gibi DisplayOnPanel içerisinden Stok gridini içeren frame'i create ediyoruz ve MainForm'daki client frame'i içerecek olan Panel'in içine yerleştiriyoruz. Ancak datayla ilgili herhangi bir işlem yapmadık henüz.

Şimdi xxxDisplayer ve MainForm arasındaki bağlantıyı oluşturacak Container kaydını yapalım! Bunun için uRegistration adlı bir Unit oluşturacağız.

unit uRegistration;

interface

uses
  Spring.Container, uInterfaces;

procedure RegisterInterfaces;

implementation
uses
      uInventoriesDisplayer
    ;

procedure RegisterInterfaces;
begin
  GlobalContainer.RegisterType<IDisplayOnPanel, TInventoriesDisplayer>('inventories');

  GlobalContainer.Build;
end;

end.


ve bu RegisterInterfaces rutinini MainForm'un OnCreate olayında çağırmamız gerekiyor.

procedure TMainForm.FormCreate(Sender: TObject);
begin
  RegisterInterfaces;
end;
Ve Stoklar düğmesine bastığımızda yapacaği işlemleri de yazalım :
procedure TMainForm.btnStoklarClick(Sender: TObject);
begin
  ViewClientFrame('inventories');
end;

procedure TMainForm.ViewClientFrame(const aFrameName: string);
var
  iScr : IDisplayOnPanel;
  i : integer;
begin
  for i:=pnlForClientFrames.ControlCount-1 downto 0 do
    if pnlForClientFrames.Controls[i] is TFrame then
      pnlForClientFrames.Controls[i].Free;
  iScr := GlobalContainer.Resolve<IDisplayOnPanel>(aFrameName);
  iScr.DisplayOnPanel(pnlForClientFrames);
end;


Burada ViewClientFrame() adlı bir procedure oluşturduk ve event'den ekranın kayıt ismini vererek bunu çağırdık. Bunun nedeni bu işlemin her buton için tekrarlanacağıydı. Bunları tekrar tekrar yazmamak için bu şekilde yazdık.
Şimdi sırada DB connection kısmı var, onu da yazdığımızda çerçeveyi tamamlamış olacağız.
Bunun için önce uInterfaces.pas kod birimine şu interafce'i ekliyoruz;

  IMyConnection = interface
    ['{3AD307A1-E323-4A65-9AE7-B25436A5DC1F}']
    function GetQuery(aSql : String) : TDataset;
  end;

Ve ardından yeni bir Unit içinde bunun uygulamasını yazıyoruz :

unit uMyADOConnection;

interface

uses DB, ADODB, uInterfaces;

type
  TMyADOConnection = class(TInterfacedObject, IMyConnection)
  private
    fConnection : TADOConnection;
  public
    constructor Create(aConnection : TADOConnection);
    function GetQuery(const aSql : String) : TDataset;
  end;

implementation

{ TMyADOConnection }

constructor TMyADOConnection.Create(aConnection : TADOConnection);
begin
  inherited Create;
  fConnection := aConnection;
  fConnection.LoginPrompt := False;
  fConnection.Connected := True;
end;

function TMyADOConnection.GetQuery(const aSql: String): TDataset;
var
  aQuery : TADODataset;
begin
  aQuery := TADODataset.Create(fConnection);
  aQuery.Connection := fConnection;
  aQuery.CommandText := aSql;
  aQuery.Open;
  Result := aQuery;
end;

end.

uRegistration biriminde Connection ile ilgili düzeltmeler yapacağız.
unit uRegistration;

interface

uses
  Spring.Container, uInterfaces;

procedure RegisterInterfaces(aMyConnection : IMyConnection);

implementation
uses
      uInventoriesDisplayer
    ;

procedure RegisterInterfaces(aMyConnection : IMyConnection);
begin
  GlobalContainer.RegisterType<IDisplayOnPanel, TInventoriesDisplayer>('inventories');

  GlobalContainer.RegisterInstance<IMyConnection>(aMyConnection, 'connection');

  GlobalContainer.Build;
end;

end.
MainForm üzerine Tool Palette'den bir TADOConnection ekleyeceğiz. Bunun DB ile bağlantısını kurup OnCreate event'inde bazı değişiklikler yapacağız. 


procedure TMainForm.FormCreate(Sender: TObject);
begin
  myConnection := TMyAdoConnection.Create(ADOConnection1);
  RegisterInterfaces(myConnection);
end;
Son olarak ta View.Inventories.pas'ta yapılandırıcıyı buraya koyacağız ve sonunda şu şekle getireceğiz.

unit View.Inventories;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Vcl.Grids,
  Vcl.DBGrids, Vcl.ExtCtrls,
  Spring.Container,
  uInterfaces;

type
  TframeInventories = class(TFrame)
    Panel1: TPanel;
    Panel2: TPanel;
    DBGrid1: TDBGrid;
    DataSource1: TDataSource;
  private
    { Private declarations }
  public
    constructor Create(AOwner : TComponent); override;
  end;

implementation

{$R *.dfm}

{ TframeInventories }

constructor TframeInventories.Create(AOwner: TComponent);
var
  aConnection: IMyConnection;
begin
  inherited;
  aConnection := GlobalContainer.Resolve<IMyConnection>('connection', []); //<IMyConnection>;
  DataSource1.DataSet := aConnection.GetQuery('select * from InventoryItem order by InventoryItemId');
end;

end.
Bu halinin kodlarını şu linkten indirebilirsiniz : DITest kodları 









Örnek DI uygulaması 1-Veritabanı oluşturulması

Tüm bu örnekleri çalıştırabilmek için öncelikle örnek veritabanını oluşturmamız gerekiyor.

Bu nedenle gerekli 5 adet tabloyu çok kısa şekilde SQL yaratma cümleciklerini paylaşacağım. Bunları MS Sql Server Management Studio'da çalıştırırsanız veritabanınız oluşturulacaktır.

create db_sample;
use db_sample;

create table Customer(
  CustomerId integer not null identity(1,1) primary key,
  CustomerName nvarchar(100) null,
  AddressLine1 nvarchar(50) null,
  AddressLine2 nvarchar(50) null,
  AddressLine3 nvarchar(50) null,
  City nvarchar(50) null,
  Country nvarchar(50) null,
  PostCode nvarchar(20) null,
  ContactPerson nvarchar(50) null,
  Phone varchar(15) null,
  Fax varchar(15) null,
  ContactEMail nvarchar(100) null,
  CurrencyTypeId integer,
  TotalDebit float default 0,
  TotalCredit float default 0,
  Balance float default 0,
  PaymentDueDate integer default 0,
  InBlackList bit not null default 0,
  IsDeleted bit not null default 0,
  LastUpdatedDT datetime default getutcdate(),
  LastModifyingUser integer
);


insert into [dbSample].dbo.Customer 
(CustomerName, AddressLine1, AddressLine2, AddressLine3, City, Country, ContactPerson, Phone, Fax, ContactEMail) 
values
('Kart Dekorasyon Malzemeleri ve Müt.Hiz. A.Ş.', 'Bedesten Sok. Katmerli Han No:4/13 Kat:3', 'Karaköy', '', 'İstanbul', 'Türkiye', 'Ahmet Alagöz', '0532717717', '02127127272', 'a-alagoz@gmail.com'),
('Damak Şekercilik ve Pastacılık Ltd.', 'Limon Sok. Hacıoğlu Ap. No:12/1', 'Göztepe', '', 'İstanbul', 'Türkiye', 'Murat Yapan', '05327177818', '02127127273', 'muratyapan@damak.com.tr');

create table Supplier(
  SupplierId integer not null identity(1,1) primary key,
  SupplierName nvarchar(100) null,
  AddressLine1 nvarchar(50) null,
  AddressLine2 nvarchar(50) null,
  AddressLine3 nvarchar(50) null,
  City nvarchar(50) null,
  Country nvarchar(50) null,
  PostCode nvarchar(20) null,
  ContactPerson nvarchar(50) null,
  Phone varchar(15) null,
  Fax varchar(15) null,
  ContactEMail nvarchar(100) null,
  CurrencyTypeId integer,
  TotalDebit float default 0,
  TotalCredit float default 0,
  Balance float default 0,
  PaymentDueDate integer default 0,
  InBlackList bit not null default 0,
  IsDeleted bit not null default 0,
  LastUpdatedDT datetime default getutcdate(),
  LastModifyingUser integer
);

insert into Supplier 
(SupplierName, AddressLine1, AddressLine2, AddressLine3, City, Country, ContactPerson, Phone, Fax, ContactEMail) 
values
('Kartel Yemek ve İkram Üretim ve Satış A.Ş.', '', 'Ümraniye', '', 'İstanbul', 'Türkiye', 'Mehmet Yurdan', '0532717719', '02127127274', 'mehmetyurdan@gmail.com'),
('Alaylı Otomotiv San. ve Tic. A.Ş.', 'Yalı Cad. Nur Han No:15/44 Kat 4', 'Karaköy', '', 'İstanbul', 'Türkiye', 'Yelda Saran', '05327177819', '02127127275', 'yeldasaran@alayliotomotiv.com.tr');

create table InventoryItem(
  InventoryItemId integer not null identity(1,1) primary key,
  InventoryGroupId integer not null,
  MainSupplierId integer null,
  ItemCode nvarchar(30) not null,
  ItemName nvarchar(100) not null,
  SpecCode nvarchar(20) null,
  StoragePlace nvarchar(100) null,
  PictureFileName nvarchar(100) null,
  Measurements nvarchar(50) null,
  CurrencyTypeId integer null,
  BuyingPrice float null,
  SellingPrice float null,
  VatPercentage float null,
  TotalIncomingQtty float default 0,
  TotalOutgoingQtty float default 0,
  TotalIncomingAmount float default 0,
  TotalOutgoingAmount float default 0,
  OtvAmount float default 0,
  UnitDesc nvarchar(10) null,
  ReorderPoint float default 0,
  BufferStock float default 0,
  IsDeleted bit not null default 0,
  LastUpdatedDT datetime default getutcdate(),
  LastModifyingUser integer
);

insert into Inventory 
(InventoryGroupId, MainSupplierId, ItemCode, ItemName,                        SpecCode, StoragePlace, Measurements, CurrencyTypeId, BuyingPrice, SellingPrice, VatPercentage, OtvAmount, UnitDesc, ReorderPoint, BufferStock)
  values
(1,                0,              'KAG80-1',	  'Kağıt 80 gram 1. hamur',				 '',       'C18R5',      'A4,80gr',    null, 15,  19,  18, null, 'Paket', 100, 20),
(1, 0,							   'KAG90-1',	  'Kağıt 90 gram 1. hamur',				 '',       'C18R6',      'A4,90gr',    null, 20,  25,  18, null, 'Paket', 40, 5),
(2, 0,                             'DEFHMO160-3', 'Defter Harita Metot 1 orta 3. hamur', '',	   'C18R4',		 'B3,60yaprak',null, 10,  13,  18, null, 'Adet',  90, 10),
(3, 0,                             'RSKLSA3',     'Resim Kalemi Siyah A3',				 'kkk',    'R-B18',      '',           2,    1.2, 1.4, 18, null, 'Adet', 80, 10);


create table PurchaseInvoiceHeader(
  PurchaseInvoiceHeaderId integer not null identity(1, 1) primary key,
  InvoiceDate datetime not null,
  InvoiceNumber nvarchar(20),
  SupplierId integer not null,
  SpecCode nvarchar(20) null,
  InvDescription nvarchar(100) null,
  CurrencyTypeId integer,
  CurrencyRate float,
  TotalAmount float,
  VatPercentage1 float,
  VatAmount1 float,
  VatPercentage2 float,
  VatAmount2 float,
  VatPercentage3 float,
  VatAmount3 float,
  TotalOtvAmount float,
  DueDate integer default 0,
  Paid bit default 0,
  MailAddress1 nvarchar(50) NULL,
  MailAddress2 nvarchar(50) NULL,
  MailAddress3 nvarchar(50) NULL,
  MailCity nvarchar(50) NULL,
  MailCountry nvarchar(50) NULL,
  MailPostCode nvarchar(20) NULL,
  IsDeleted bit not null default 0,
  LastUpdatedDT datetime default getutcdate(),
  LastModifyingUser integer,
  CONSTRAINT FK_PurchaseInvoiceHeader_Supplier FOREIGN KEY (SupplierId)     
    REFERENCES Supplier (SupplierId)
);

create table SalesInvoiceHeader(
  SalesInvoiceHeaderId integer not null identity(1,1) primary key,
  InvoiceDate datetime not null,
  InvoiceNumber nvarchar(20),
  CustomerId integer not null,
  SpecCode varchar(20) null,
  InvDescription nvarchar(100) null,
  CurrencyTypeId integer,
  CurrencyRate float,
  TotalAmount float,
  VatPercentage1 float,
  VatAmount1 float,
  VatPercentage2 float,
  VatAmount2 float,
  VatPercentage3 float,
  VatAmount3 float,
  TotalOtvAmount float,
  DueDate integer default 0,
  Paid bit default 0,
  DeliveryAddress1 nvarchar(50) null,
  DeliveryAddress2 nvarchar(50) null,
  DeliveryAddress3 nvarchar(50) null,
  DeliveryCity nvarchar(50) null,
  DeliveryCountry nvarchar(50) null,
  DeliveryPostCode nvarchar(20) null,
  IsDeleted bit not null default 0,
  LastUpdatedDT datetime default getutcdate(),
  LastModifyingUser integer,
  CONSTRAINT FK_SalesInvoiceHeader_Customer FOREIGN KEY (CustomerId)     
    REFERENCES Customer (CustomerId)
);



21 Kasım 2023 Salı

Dependency Injection in Delphi TÜRKÇE - Nick Hodges

Bağımlılık Enjeksiyonu Nedir?

Giriş

Üç çocuğum var. Artık yaşlandılar ama küçükken onlarla yemek pişirmek eğlenceliydi. Daha sonra yemekten keyif alacakları bir pasta, kurabiye veya başka şekerlemeler pişirirdik. Ancak bir sorun vardı; pişirirken büyük bir karışıklık yarattık. Her yerde ve her tarafımızda un, şeker ve her türlü şey vardı. Pişirme süreci bize büyük bir temizlik işi bıraktı. Pek çok işe yaradı. Elbette eğlendik ama karmaşa yine de oradaydı.

Karışıklıktan nasıl kaçınılır? Sanırım daha dikkatli olabiliriz ama çocuklar çocuk olacaktır. Çoğu zaman doğum günü pastası pişirirdik. Elbette, pasta pişirmenin tüm zorluğuna ve karmaşasına bir alternatif, mağazaya gidip önceden hazırlanmış bir pasta satın almak olacaktır. Daha da iyi bir çözüm, sipariş üzerine doğum günü pastası teslim edecek ve doğum günü pastanız için tam olarak istediğiniz şeyi kapınıza bırakacak bir fırın bulmak olacaktır.

Şimdi burada dikkate alınması gereken başka bir şey var. Diyelim ki teslimatçı geldi ve çamurda yürüyordu, çizmeleri kirliydi ve şiddetli bir öksürüğü vardı. Onu evine mi alacaksın? Hayır tabii değil. Muhtemelen kapı aralığından ona bir kez bakar ve ona pasta kutusunu verandaya bırakmasını ve çimenlikten defolup gitmesini söylersiniz. Sanırım pasta konusunda da son derece dikkatli olursunuz, ancak tartışma adına pastanın iyi sarıldığını ve teslimatçının yaydığı balgamdan korunduğunu varsayacağız. Başka bir deyişle, teslimatçıyla etkileşiminizin mümkün olduğunca az olmasını istiyorsunuz ama yine de pastayı istiyorsunuz. Çocuğunuz doğum günü pastasının mumlarını üflemezse oldukça üzülecektir. Pastayı karışıklık olmadan ve teslimatçıyla en ince etkileşimlerle istiyorsunuz.

Hatta doğum günü pastasına bağımlılığınız olduğu ve fırının pastayı size teslim ederek bu bağımlılığı enjekte ettiği bile söylenebilir. Hmm.

Veya diyelim ki süpermarkettesiniz ve yiyecek dolu bir alışveriş sepetiniz var. Onları kasa görevlisine götürürsün, o da hepsini arar. “Bu 123,45 dolar olacak” diyor. Ee ne yapıyorsun? Ona cüzdanınızı verip nakit ya da kredi kartı bulmasını mı istiyorsunuz? Tabii ki hayır - cüzdanınızla etkileşimi mümkün olduğunca minimumda tutmak istiyorsunuz - onun etrafta dolaşmasını istemiyorsunuz. Ne olacağını kim bilir. Bunun yerine adama nakit verirsiniz ya da kredi kartınızı çıkarıp ona verirsiniz. Ya da daha iyisi, kartı kendiniz kaydırırsınız, böylece kasa görevlisi kartınıza asla dokunmaz. Yine, bu etkileşimin mümkün olduğunca az olmasını istiyorsunuz. Görevliyle aranızdaki iletişimi minimumda tutmak istediğinizi söyleyebilirsiniz.

Bir tane daha. Bir ev almayı düşündüğünüzü söyleyin. Şehrin her yerini ararsınız ve sonunda gerçekten beğendiğiniz bir yer bulursunuz. Konumu iyi, okul bölgesi iyi, mahalle iyi. Tek bir sorun var; tüm elektrikli cihazlar evin içine kabloyla bağlı. Tüm lambalar, ekmek kızartma makinesi, saç kurutma makinesi, her şey kablolu. Evde priz yok. Her şey doğrudan elektrik sistemine bağlanmıştır, dolayısıyla hiçbir şeyi kolayca değiştiremezsiniz. Elektrikli cihazlar ile elektrik sistemi arasındaki normal arayüzler (elektrik fişleri) mevcut değildir. Ekmek kızartma makinenizi elektrikçi çağırmadan değiştiremezsiniz. Arayüz eksikliğinin işleri çok zorlaştırdığı söylenebilir ve bu nedenle evi satın almamaya karar veriyorsunuz.

Burada bir model (şablon) görüyor musun?

Tamam, bu kadar hikaye yeterli. Sadece söyleyeceğim. Kodunuzda nesneler arasındaki etkileşimin mümkün olduğunca ince ve temiz olması gerekir. Tam olarak ihtiyacınız olanı istemelisiniz, daha fazlasını değil. Bir şeye ihtiyacınız varsa, onu kendiniz yaratmaya çalışmak yerine, onu istemelisiniz, size “teslim edilmesini” sağlamalısınız. Bu şekilde tıpkı pasta, kasiyer ve evde olduğu gibi her şey düzenli, temiz ve esnek kalır. Düzgün, temiz ve esnek bir kodun kulağa oldukça hoş geldiğini düşünüyorum.

Bağımlılık Enjeksiyonunun özü budur. Gerçekten bundan başka bir şey değil. Bağımlılık Enjeksiyonu, on sentlik bir konsept için yirmi beş dolarlık bir terimdir: Bir şeye ihtiyacınız varsa, onu isteyin. Bir şeyleri kendiniz yaratmayın; bunu başkasına ittirin. Herhangi bir maddenin her sınıfı muhtemelen diğer sınıfların yardımına ihtiyaç duyacaktır. Sınıfınızın yardıma ihtiyacı varsa isteyin; "kendisi yapsın" diye uğraşmayın. Unutmayın, her "kendi başına yapsın" diye çalıştığınızda, sabit, katı şekilde kodlanmış bir bağımlılık yaratırsınız. Ve yine küresel (global) değişkenler gibi bunlardan kaçınılmalıdır.

Göreceğimiz gibi aslında bu kadar basit.

Dikkat edilmesi gereken bir nokta -ki bu kitabın ilk bölümünde üzerinde duracağım bir noktadır- henüz "Konteyner" kelimesini kullanmamış olmamızdır. Aslında işte benim bir tweetim:

Bu biraz tuhaf gelebilir ama önemli bir noktaya işaret ediyor: "Bağımlılık Enjeksiyonu Yapmak" ve "Bağımlılık Enjeksiyonu konteyneri kullanmak" gerçekten iki farklı şeydir. Bunlar birbiriyle ilişkilidir (ikincisi birincinin ölçeklendirilmesini kolaylaştırır), ancak aynı şey değildirler. Aslında DI Konteyneri hakkında tekrar konuşmadan önce çok fazla alana değineceğim.

Peki Bağımlılık Enjeksiyonu Tam Olarak Nedir?

Şu ana kadar muhtemelen DI'nin tam olarak ne olduğunu merak ediyorsunuz. Harika “.Net'te Dependency Injection” kitabının yazarı Mark Seemann'a göre Dependency Injection, “gevşek bağlı kod geliştirmemize olanak tanıyan bir dizi yazılım tasarım ilkesi ve modelidir.” Bu iyi bir tanım ama biraz antiseptik. Biraz daha derinlemesine inceleyelim.

Eğer bir bağımlılık enjekte edecekseniz, bağımlılığın ne olduğunu bilmeniz gerekir. Bağımlılık, belirli bir sınıfın işini yapmak için ihtiyaç duyduğu herhangi bir şeydir (ör. alanlar, diğer sınıflar vb.). TClassA'yı derlemek için TClassB'nin mevcut olması gerekiyorsa, o zaman TClassA, TClassB'ye bağımlıdır. Veya başka bir deyişle TClassB, TClassA'nın bağımlılığıdır. Bağımlılıklar, siz bir tane oluşturduğunuzda yaratılır. İşte bir örnek:

type 
  TClassB = class 
  end; 

  TClassA = class 
  private 
    FClassB: TClassB; 
  public 
    constructor Create
  end; 

constructor TClassA.Create
begin 
  FClassB := TClassB.Create; 
end; 

Yukarıdaki kodda, TClassA'da TClassB'ye sabit kodlanmış bir bağımlılık oluşturduk. Tıpkı daha önce tartıştığımız saç kurutma makinesi veya ekmek kızartma makinesi gibi; sınıfa kablolarla bağlı. TClassA tamamen TClassB'ye bağımlıdır. TClassB, TClassA'ya "bağlanmıştır". Sıkıca birleştirilmiş. Gerçekten alabildiğiniz kadar sıkı bir şekilde birleşmişsiniz. Ve sıkı bağlaşım kötüdür.

Bağlaşım (coupling) Hakkında Birkaç Kelime

Yukarıda gördüğümüz gibi bağlaşım, bir şeyin diğerine bağımlı olması kavramıdır. Sıkı bağlaşım, her şeyin gerçekten birbirine bağlı olduğu ve sıkıca bağlanmış oldukları zamandır. B sınıfının tam tanımı olmadan A sınıfını derleyemezsiniz. B kalıcı olarak A'ya yapışmıştır. Sıkı bağlaşım kötüdür; esnek olmayan kod oluşturur. Başka birine kelepçelenseydiniz hareket etmenin ne kadar zor olacağını bir düşünün. Sabit kodlanmış bağımlılıklar oluşturduğunuzda bir sınıf böyle hisseder.

İstediğiniz şey, sınıflarınız ve modülleriniz arasındaki bağlaşımı mümkün olduğunca "gevşek" tutmaktır. Yani, bağımlılıklarınızın mümkün olduğu kadar zayıf olmasını istiyorsunuz. Müşteri adına ihtiyaç duyan bir sınıfınız varsa yalnızca müşteri adını girin. Müşterinin tamamını aktarmayın. Ve göreceğimiz gibi, sınıfınızın başka bir sınıfa ihtiyacı varsa, o sınıfın bir soyutlamasını (genellikle bir arayüzü) aktarın. Arayüz bir duman bulutu gibidir; orada bir şey vardır ama onu gerçekten kavrayamazsınız.

Bu kavram o kadar önemlidir ki onu bir sonraki bölümde daha ayrıntılı olarak tartışacağız.

Bu sabit kodlanmış, sıkı bir şekilde birleştirilmiş bağımlılıkları nasıl yaratırsınız? Bunları diğer sınıfların içinde bir şeyler yaratarak yaratırsınız. Yukarıdaki örneğe bakın. TClassA'nın yapıcısı, TClassB'nin bir örneğini oluşturur ve onu dahili olarak saklar. Bu Create çağrısı bağımlılığı yaratır. TClassA'yı derlemek için TClassB'ye ihtiyacınız var.

Ne yapmalı?

Yapılacak ilk şey TClassB'yi TClassA'nın içinde yaratmamak. Bunun yerine, bağımlılığı yapılandırıcı (constructor) aracılığıyla "enjekte etmek":

type 
  TClassB = class 
  end; 
  TClassA = class 
  private 
    FClassB: TClassB; 
  public 
    constructor Create(aClassB: TClassB); 
  end; 
  
constructor TClassA.Create(aClassB: TClassB); 
begin 
  FClassB := aClassB; 
end; 

Bunu yaparak bağlaşımı biraz gevşetmiş olursunuz. Öncelikle artık istediğiniz TClassB örneğini aktarabileceğinizi unutmayın. Eğer mantıklıysa, soyundan bile geçebilirsiniz. Hala TClassB'ye bağlısınız ve TClassA hala TClassB olmadan derlenemiyor, ancak bağlaşımı bir dokunuşla gevşeterek biraz esneklik eklediniz. TClassA ve TClassB hala bağlaşıktır ancak daha gevşek bir şekilde bağlıdırlar.

Yani bu noktada iki tür bağlaşımımız var: birinci sınıfın aslında ikinci sınıfın bir örneğini oluşturduğu yer ve birinci sınıfın ikinci sınıfa ihtiyaç duyduğu, yani enjekte edildiği yer. İkincisi, daha az (daha gevşek) bağlaşım içermesi nedeniyle birinciye tercih edilir. Bir sonraki bölümde eşleşme hiyerarşisine, yani "uyum-connassence" adı verilen bir kavrama bakacağız.

Ve bu, arkadaşlar, DI'nin özüdür. Bağımlılıklarınızı oluşturmak yerine enjekte edin. Bu kadar. Bu Bağımlılık Enjeksiyonu. Tam burada durabilirim ve eğer size öğrettiğim tek şey bu olsaydı, kodunuz bugün olduğundan daha iyi durumda olurdu (çünkü kabul edin, kod tabanınız sabit kodlanmış bağımlılıklarla dolu, değil mi?). Kitabı burada bitirebilirim ve alet çantanızda işleri gerçekten geliştirecek yeni bir araç olur.

Ama elbette, aslında bu kadar basit değil. Bundan daha fazlası var ve projeler büyüdükçe işler karmaşıklaşıyor, ancak ilk örnek olarak bu, DI'nın ne olduğunu ve nasıl çalıştığını açıklama konusunda uzun bir yol kat ediyor. Önümüzdeki bölümlerde bunu daha derinlemesine inceleyeceğiz ancak bu basit tekniğin gücünü anladıysanız ve gördüyseniz, daha iyi, daha temiz, bakımı daha kolay ve daha esnek kod yazma yolundasınız demektir.

Uyulması Gereken Temel İlkeler

Bu kitaba hakim olacak ve Bağımlılık Enjeksiyonu konusuyla ilgili olacak birkaç temel prensip vardır. Bunlar aşağıdaki gibidir:

Uygulamalara(implementations) Değil, Soyutlamalara(abstractions) Karşı Kod

"Dörtlü Çete"den ("Tasarım Desenleri" kitabının yazarları) Erich Gamma'nın bu ifadeyi icat ettiği düşünülmektedir ve bu güçlü ve önemli bir fikirdir. Yeni geliştiricilere yalnız tek bir şey öğretecek olsanız, o da bu aforizma olmalıdır. Soyutlamalar - genellikle arayüzlerdir (interfaces), ancak her zaman değil (aşağıya bakın) - esnektir. Arayüzler (veya soyut sınıflar) birçok yolla uygulanabilir. Arayüzler, uygulama tamamlanmadan önce bile kodlanabilir. Bir uygulamaya kod yazarsanız sıkı sıkıya bağlı ve esnek olmayan bir sistem yaratırsınız. Kendinizi tek bir uygulamaya (implementation'a) kilitlemeyin. Bunun yerine soyutlamalar kullanın ve kodunuzun esnek, yeniden kullanılabilir ve esnek olmasına izin verin.

Asla Yaratılmaması Gereken Şeyleri Yaratmayın

Sınıflarınız, bir sınıfın yalnızca tek bir şey yapması gerektiği fikri olan Tek Sorumluluk İlkesine uymalıdır. Eğer bunu yaparlarsa, o zaman bir şeyler yaratmamaları gerekir çünkü bunu yaptıklarında iki şey yapmış olurlar. Bunun yerine, ihtiyaç duydukları işlevselliği istemeli ve başka bir şeyin bu işlevselliği yaratmasına ve sağlamasına izin vermelidirler.

Yaratılabilirler ve Enjekte Edilebilirler 

Peki ne yaratılmalı? Aslında ilgilenmemiz gereken iki farklı nesne türü var: "Yaratılabilirler" ve "Enjekte Edilebilirler."

Yaratılabilirler, devam edilmesi ve yaratması gereken sınıflardır. Bunlar yaygın ve iyi bilinen RTL veya yardımcı program sınıflarıdır. Delphi geliştiricileri için bunlar TStringList ve TList<T> gibi şeylerdir. Genel olarak Delphi çalışma zamanındaki sınıflar Yaratılabilirler olarak değerlendirilmelidir. Bunun gibi sınıflar enjekte edilmemeli, sınıflarınız tarafından oluşturulmalıdır. Çoğunlukla kısa ömürleri vardır ve sıklıkla tek bir yöntemin süresinden daha uzun yaşamazlar. Eğer sınıfın tamamı için gerekliyse yapılandırıcıda oluşturulabilirler. Bir Yaratılabilir'in yapılandırıcısına yalnızca diğer Yaratılabilirler aktarılmalıdır.

Enjekte edilebilirler ise asla doğrudan oluşturmak istemediğimiz sınıflardır. Bunlar asla bir bağımlılığı sabit kodlamak istemediğimiz ve her zaman Bağımlılık Enjeksiyonu yoluyla aktarılması gereken sınıf türleridir. Normalde bir yapılandırıcı içerisine bağımlılıklar olarak istenecektir. Yukarıdaki kurala uygun olarak, enjekte edilebilir öğelere, bir örneğe doğrudan referanslar değil, arayüzler aracılığıyla referans verilmelidir. Enjekte edilebilirler çoğunlukla iş mantığınızın (business logic) bir parçası olarak yazdığınız sınıflar olacaktır. Bunların daima bir soyutlamanın, genellikle bir arayüzün arkasına gizlenmeleri gerekir. Enjekte edilebilirlerin yapıcılarında başka enjekte edilebilirler isteyebileceğini de unutmayın.

Yapıcıları Basit Tutun

Yapıcılar basit tutulmalıdır. Bir sınıfın yapılandırıcısı herhangi bir "iş" yapmamalıdır; yani sıfır olup olmadığını kontrol etmek, Yaratılabilir Öğeler oluşturmak ve bağımlılıkları daha sonra kullanmak üzere depolamak dışında hiçbir şey yapmamalıdır. Herhangi bir kodlama mantığı içermemelidirler. Bir sınıfın yapıcısında sıfır(nil) olup olmadığını kontrol etmeyen bir 'if' cümlesi, o sınıfın iki sınıfa bölünmesi için bir çığlıktır. (if ifadesini içermeyen, sıfır(nil) değerli parametreleri kontrol etmenin yolları vardır. Daha sonraki bir bölümde "Asla sıfırı kabul etme" kavramını ele alacağız). Karmaşık bir yapılandırıcı, sınıfınızın çok fazla şey yaptığını gösteren açık bir işarettir. Yapıcıları kısa, basit ve her türlü mantıktan (logic) uzak tutun.

Uygulama(implementation) Hakkında Hiçbir Şey Varsaymayın

Arayüzler elbette bir uygulama olmadan işe yaramaz. Ancak bir geliştirici olarak siz, bu uygulamanın ne olduğu konusunda hiçbir zaman varsayımda bulunmamalısınız. Yalnızca arayüzün yaptığı sözleşmeye göre kodlama yapmalısınız. Uygulamayı yazmış olabilirsiniz, ancak bu uygulamayı göz önünde bulundurarak arayüze karşı kodlama yapmamalısınız. Başka bir deyişle, sanki arayüzün tamamen yeni ve daha iyi bir uygulaması çok yakındaymış gibi arayüzünüze göre kodlayın. İyi tasarlanmış bir arayüz size ne yapmanız gerektiğini ve nasıl kullanmanız gerektiğini anlatacaktır. Bu arayüzün uygulanması, arayüzü kullanımınız açısından önemsiz olmalıdır.


Bir Arayüzün Soyutlama Olduğunu Düşünmeyin

Arayüzler güzel ve kesinlikle her zaman onları övüyorum. Ancak her arayüzün bir soyutlama olmadığının farkına varmak önemlidir. Örneğin, arayüzünüz sınıfınızın genel kısmının tam bir temsiliyse, gerçekten hiçbir şeyi "soyutlamıyorsunuz", değil mi? (Bu tür arayüzlere C++ başlık dosyalarına benzedikleri için “başlık arayüzleri” adı verilir). Sınıflardan çıkarılan arayüzler yalnızca o sınıfa kolayca sıkı bir şekilde bağlanabilir, bu da arayüzü bir soyutlama olarak işe yaramaz hale getirir. Son olarak, soyutlamalar "sızdıran" olabilir, yani uygulamalarına ilişkin belirli uygulama ayrıntılarını ortaya çıkarabilirler. Sızdıran soyutlamalar da normalde belirli bir uygulamaya bağlıdır. (Bu kavram hakkında daha fazla bilgiyi Mark Seemann'ın [http://bit.ly/2awOhmn]http://bit.ly/2awOhmn] adresindeki mükemmel blog yazısında okuyabilirsiniz.)

Sonuç

Tamam, bu Bağımlılık Enjeksiyonu fikrine temel bir giriş görevi görmelidir. Bağımlılık Enjeksiyonu belirli bir amaca yönelik bir araçtır ve bu amaç gevşek bir şekilde bağlanmış koddur.

Açıkçası bundan daha fazlası var, dolayısıyla bu kitabın geri kalanı. Ancak soyutlamalara karşı kodlama yapmanız ve ihtiyacınız olan işlevselliği istemeniz gerektiği kavramlarını anlarsanız, Dependency Injection'ı anlama ve daha iyi kod yazma yolunda iyi bir yoldasınız demektir.

Bağımlılık Enjeksiyonunun Faydaları

Bütün bunları neden yapmalıyız? Kodumuzu Dependency Injection ilkelerinin gerektirdiği şekilde düzenlemek için neden bu kadar zahmete girelim ki? Çünkü faydaları var. Bağımlılık Enjeksiyonunun faydalarından biraz bahsedelim çünkü bunlar çok sayıda ve ilgi çekicidir.

Sürdürülebilirlik – Bağımlılık Enjeksiyonunun muhtemelen temel faydası sürdürülebilirliktir. Sınıflarınız gevşek bir şekilde bağlıysa ve tek sorumluluk ilkesini (DI kullanmanın doğal sonucu) izliyorsa kodunuzun bakımı daha kolay olacaktır. Basit, bağımsız sınıfların düzeltilmesi, karmaşık, sıkı bir şekilde birleştirilmiş sınıflardan daha kolaydır. Bakımı yapılabilen kodun toplam sahip olma maliyeti daha düşüktür. Bakım maliyetleri genellikle ilk etapta kodu oluşturma maliyetini aşar; dolayısıyla kodunuzun sürdürülebilirliğini artıran her şey iyi bir şeydir. Hepimiz zamandan ve paradan tasarruf etmek istiyoruz, değil mi?

Test Edilebilirlik – Sürdürülebilirlik ile aynı doğrultuda test edilebilirlik de vardır. Test edilmesi kolay olan kod daha sık test edilir. Daha fazla test, daha yüksek kalite anlamına gelir. Yalnızca tek bir şey yapan (yine DI kullanmanın doğal sonucu olan) gevşek bağlı sınıfların birim testini yapmak çok kolaydır. Dependency Injection'ı kullanarak test çiftleri (genellikle "sahte" olarak adlandırılır) oluşturmayı çok daha kolay hale getirirsiniz. Bağımlılıklar sınıflara aktarılırsa, test çift uygulamasını geçmek oldukça basittir. Bağımlılıklar sabit kodlanmışsa bu bağımlılıklar için test çiftleri oluşturmak imkansızdır. Gerçekte test edilen test edilebilir kod, kalite kodudur. Veya en azından test edilmemiş koddan daha kalitelidir. Birim testlerinin zaman kaybı olduğu iddiasını kabul etmekte zorlanıyorum; onlar benim için her zaman zaman ayırmaya değer. (Elbette bunun tartışmalı olmasını garip bulan tek kişi ben değilim?)

Okunabilirlik – DI kullanan kod daha basittir. Tek Sorumluluk İlkesini takip eder ve böylece daha küçük, daha kompakt ve noktasal sınıflarla sonuçlanır. Yapıcılar o kadar karmaşık ve mantıkla dolu değiller. Sınıflar daha net bir şekilde tanımlanmış olup neye ihtiyaçları olduğu açıkça bildirilmektedir. Tüm bunlardan dolayı DI tabanlı kod daha okunabilirdir. Ve daha okunabilir olan kod daha kolay korunur-sürdürülür.

Esneklik – Gevşek bağlı kod – yine DI kullanmanın sonucu – daha esnektir ve farklı şekillerde kullanılabilir. Tek bir şey yapan küçük sınıflar daha kolay bir şekilde yeniden bir araya getirilebilir ve farklı durumlarda yeniden kullanılabilir. Küçük sınıflar Legolar(tm) gibidir; daha hacimli ve daha az esnek olan Duplo(tm) bloklarının aksine, çok sayıda şey oluşturmak için kolaylıkla bir araya getirilebilirler. Kodu yeniden kullanabilmek zamandan ve paradan tasarruf sağlar. Tüm yazılımların değişebilmesi ve yeni gereksinimlere uyum sağlayabilmesi gerekiyor. Bağımlılık Enjeksiyonu kullanan gevşek bağlı kod esnektir ve bu değişikliklere uyum sağlayabilir.

Genişletilebilirlik – Bağımlılık Enjeksiyonunu kullanan kod, daha genişletilebilir bir sınıf yapısıyla sonuçlanır. Kod, uygulamalar (implementation) yerine soyutlamalara güvenerek belirli bir uygulamayı kolayca değiştirebilir. Soyutlamalara karşı kod yazdığınızda, yaptığınız işin çok daha iyi bir uygulamasının çok yakında olduğu fikriyle kodlayabilirsiniz. Küçük, esnek sınıflar miras veya bileşim yoluyla kolayca genişletilebilir. Bir uygulamanın kod tabanı hiçbir zaman statik kalmaz ve kod tabanınız büyüdükçe ve yeni gereksinimler ortaya çıktıkça büyük olasılıkla yeni özellikler eklemeniz gerekecektir. Genişletilebilir kod bu zorluğun üstesinden gelebilir.

Ekip Geliştirme – Bir ekipteyseniz ve bu ekibin bir proje üzerinde birlikte çalışması gerekiyorsa (bu ne zaman doğru değildir?), Dependency Injection ekip gelişimini kolaylaştıracaktır. (Yalnız çalışıyor olsanız bile, ileride işinizin birilerine devredilmesi ihtimali çok yüksektir). Dependency Injection sizi uygulamalara(impementation'lara) değil soyutlamalara göre kodlamaya çağırır. Birlikte çalışan ve her biri diğerinin çalışmasına ihtiyaç duyan iki ekibiniz varsa, uygulamaları yapmadan önce soyutlamaları tanımlayabilirsiniz ve ardından her takım, uygulamalar yazılmadan önce bile soyutlamaları kullanarak kendi kodunu yazabilir. Ayrıca kod gevşek bir şekilde bağlı olduğundan bu uygulamalar birbirine bağlı olmayacak ve bu nedenle ekipler arasında kolaylıkla bölünebilir.

İşte işte buradasın. Bağımlılık Enjeksiyonu, ekip üyeleri arasında kolayca dağıtılabilen, bakımı yapılabilir (sürdürülebilir), test edilebilir, okunabilir, esnek ve genişletilebilir kodla sonuçlanır. Herhangi bir geliştiricinin tüm bunları istemeyeceğini hayal etmek zor görünüyor.

Bağlaşıma Daha Yakından Bir Bakış: Uyum (connassence)

Giriş

Dikkat ettiyseniz bağlılığı sevmediğimi fark etmişsinizdir. Sıkıca bağlanmış kod beni rahatsız ediyor. Blog yazılarımda, kitaplarımda, her yerde bundan bahsediyorum. Sıkı bağlantı kötüdür. Gevşek bağlantı istediğimizi ve sıkı bağlantıdan kaçınmamız gerektiğini biliyoruz. Bu bir bakıma verilmiş bir şey ama tüm bunların tam olarak ne anlama geldiğine dair çok az değerli tartışma oldu. Bunu tarif etmenin oldukça zor olduğu biliniyor. “Gördüğümde anlarım” türünden bir şeydi bu. Bağımlılık Enjeksiyonu tamamen bağlamayı azaltmakla ilgili olduğundan, bağlamanın ne olduğu hakkında biraz daha fazla bilgi içeren bu bölümü eklemenin iyi bir fikir olacağını düşündüm.

Bağlılık, iki modül arasındaki ilişkilerin ve bağlantıların bir ölçüsüdür. Kodun bir şekilde bağlanması gerekiyor, yoksa hiçbir şey yapamaz. Peki bu ölçümler nasıl yapılıyor? Tam olarak ne ölçülüyor? Bunlar zor sorular. Sıkı bağlantının kötü olduğu göz önüne alındığında, bunu mümkün olduğunca sınırlamak istiyoruz. Peki bunu tam olarak nasıl yapacağız? Bağlantı tam olarak ne halt ediyor?

Uyum (Connascence)

Neyse ki bağlılığı ölçmenin bir yolu var. Buna "uyum" (connascence) denir. ("Cuh-NAY-since" diye telaffuz edildiğini duydum) Yazılım geliştirme alanında, sistemin genel doğruluğunu korumak için iki modülden birini değiştirmek diğerinde de değişiklik gerektiriyorsa iki modülün "connascent" (uyumlu) olduğu söylenir. . Birleşmeyi düşündüğümüzde aklımıza gelen hemen hemen budur. Terim ilk kez Meilir Page-Jones tarafından eşleşmenin tam olarak ne olduğunu ölçebilmek ve nitelendirebilmek amacıyla kullanıldı. Bunu ilk olarak “Nesneye Yönelik Tasarım Hakkında Her Programcının Bilmesi Gerekenler” adlı kitabında tartıştı. Kitap 1995 yılında yayınlandı, dolayısıyla bu fikir yeni bir şey değil.

Ancak çoğu zaman olduğu gibi yirmi yıllık bu fikir ancak şimdilerde gündeme geliyor. Connascence, kodunuzdaki bağımlılığı ölçmenin bir yoludur.

Connascence iki boyutta ele alınmaktadır. Birincisi, dokuz farklı uyum düzeyi vardır. İkincisi, bu seviyelerin hepsinin belirli nitelikleri vardır. Önce bu niteliklerden bahsedeceğim, sonra da uyum düzeyleri hakkında konuşmaya geçeceğiz. Uyum düzeyleri, bir eşleşme taksonomisi oluşturmamıza ve bunun hakkında konuşmak için bize bir kelime dağarcığı vermemize olanak tanır. Bu gerçekten faydalıdır, çünkü daha önceki eşleşme tartışmaları genellikle "sıkı mı gevşek mi" konusunda çok şekilsiz bir tartışmaya dönüşüyordu; bu da olaylara pek bilimsel bir bakış açısı getirmiyordu.

Connascence'nin Nitelikleri

Connascence'ın üç niteliği vardır: Güç, Derece ve Yerellik.

Connascence'nin Gücü

Eğer onu düzeltmek daha derinlemesine ve daha zor değişiklikler gerektiriyorsa, bir Connascence (uyumluluk-uzlaşma) düzeyinin diğerinden daha güçlü olduğu söylenir. Örneğin, iki varlık arasındaki bağlaşımın azaltılması kolay ve basit bir değişiklik gerektiriyorsa, bu durumda connascence'nin, karmaşık bir değişiklik gerektiren connascence'den daha az güçlü olduğu söylenir. Bir düzeyde connascence'nin yeniden düzenlenmesi (refactoring) zorsa, bunun güçlü düzeyde bir uyum olduğu söylenir. Aşağıda açıklanan Uyum Düzeyleri artan güce göre listelenmiştir. Bu sıralama, yeniden düzenleme (refactoring) işleminize nasıl öncelik vereceğiniz konusunda size bir fikir verir. Yani, en güçlü bağlantıları daha zayıf bağlantı seviyelerine kadar yeniden düzenlemeye (refactor etmeye) çalışmalısınız.

Connascence Derecesi

Connascence derecesi, uyumluluğun meydana geldiği seviyenin bir ölçüsüdür. Connassence küçük derecede veya büyük derecede meydana gelebilir. Örneğin, iki modül tek bir referans yerine birden fazla referansla bağlanabilir. Çoklu referansların bağlantısının yüksek derecede yakınlığa sahip olduğu söylenir. Belirli bir yöntem, onu birçok dış sınıfa bağlayan birçok parametreye sahip olabilir. Böyle bir metodun-yöntemin yüksek derecede connascence'si (uyumu) vardır.

Connascence'nin Bölgesi 

Bazen bağlaşım(coupling) birbirine yakın gerçekleşir; aynı ünitede (Unit) birbirine bağlı iki sınıfınız vardır. Connascence tek bir metotla ortaya çıkabilir. Ancak bazen bu bağlaşım birbirinden çok uzakta olan iki birimde (Unit'te) meydana gelir. Bunu hepimiz gördük; uygulamanızın “sol alt” kısmında bir değişiklik yapıyorsunuz ve bu, programın “sağ üst” kısmında çok uzakta bir etki yaratıyor. Birbirine yakın olan connascence, uzak olandan daha iyidir-hayırlıdır.

Connascence'nin Düzeyleri

Bağlaşımın bazısı gereklidir. Bir uygulamada o olmadan hiçbir şey olamaz. Bununla birlikte, bağlaşımın gücünü mümkün olduğu kadar zayıf, derecesini mümkün olduğu kadar küçük ve lokalitesini mümkün olduğu kadar yakın tutmak istiyoruz. Eğer bağlaşımı ölçebilseydik, bunu yaptığımızı bilebilirdik, değil mi? Page-Jones, her biri bir öncekinden daha güçlü, daha yüksek derecede ve/veya daha uzak bir bölgede olan dokuz connascence düzeyini tanımladı. Bu bağlantı seviyelerini fark ettiğimizde, bunları daha düşük bir seviyeye indirecek şeyler yapabiliriz. Bir göz atalım ve her şeyin nasıl çalıştığını görelim.

Statik-Durağan Connascence'ler

Connascense'ın ilk beş seviyesi statiktir denilir, çünkü kodunuzu görsel olarak inceleyerek bulunabilirler.

İsim Connascence'si

İsim connascense'i, iki şeyin bir şeyin adı üzerinde anlaşmaya varması gerektiğinde ortaya çıkar. Bu, connascence'nin en zayıf biçimidir ve kendimizi sınırlamaya çalışmamız gereken biçimdir. Bu neredeyse apaçıktır ve kaçınılamaz. Bir prosedür bildirirseniz:

procedure TMyClass.DoSomething;

onu DoSomething adını kullanarak çağırmanız gerekir. Bir şeyin adını değiştirmek başka yerde değişiklik yapılmasını gerektirir. Prosedürün adını değiştirmek istiyorsanız, onu çağırdığınız her yerde değiştirmeniz gerekir. Bu açık görünüyor ve elbette bu connascence düzeyi kaçınılmazdır. Aslında bu arzu edilen bir şeydir. Bu, sahip olabileceğimiz en düşük düzeydeki bağlaşmadır ve bu yüzden onu aramalı ve en çok kullanmalıyız. Eğer birleşmemizi İsmin Uzlaşması ile sınırlandırabilirsek, çok iyi durumda oluruz.

Type (tip) Connascence'si

Tip connascence'si, iki varlığın bir şeyin türü üzerinde anlaşmaya varması gerektiğinde ortaya çıkar. En bariz örnek bir metodun parametreleridir. Bir işlevi(fonksiyonu) aşağıdaki gibi bildirirseniz:

function TSomeClass.ProcessWidget(aWidget: TWidget; aAction: TWidgetActionType): Boolean; 

bu durumda herhangi bir çağıran kod, ProcessWidget işlevinin parametreleri olarak bir TWidget ve bir TWidgetActionType iletmeli ve sonuç türü olarak bir Boolean kabul etmelidir. Delphi güçlü bir şekilde typed olarak yazılmıştır, dolayısıyla bu tür bir connascence neredeyse her zaman derleyici tarafından yakalanır.(MÖ'nün notu : typed'ı tanımlamak için untyped'ı tanımlamak gerekir. Pointer kullanıyorsanız programınız untyped'dır ve çalışma zamanında hata vermesi çok muhtemeldir. Örneğin eski TList sınıfına bir Type'ı değil yalnızca bir pointeri eklersiniz. Tamsayı, reel sayı, çeşitli bilgiler içeren bir record ve bir sınıf olabilir bunlar ve siz bunu ancak runtime'de kontrol edebilirsiniz. Oysa yeni generic'ler-soysallar kullanılarak bu tür hatalardan da kaçınılabilir duruma geldi Delphi.). Type connascence'si, İsim Connscence'si kadar zayıf değildir, ancak yine de zayıf ve kabul edilebilir bir connscence düzeyi olarak kabul edilir. Gerçekten de, o olmadan gerçekten idare edemezsiniz, değil mi? Herhangi bir şeyi yapmak için bir sınıfın yöntemini çağırabilmeniz gerekir ve türlerinizin eşleştiğinden emin olmak çok da külfetli değildir. Aslında Delphi'de kodunuzu derlemek için bile Connascence of Type aracılığıyla kodu bağlaştırmanız gerekir.

Anlam Connascence'si

Anlam Connascence'si, bileşenlerin belirli değerlerin anlamı üzerinde anlaşmaya varması gerektiğinde ortaya çıkar. Anlam Connascence'siçoğunlukla “sihirli sayıları”, yani anlamı olan ve birden fazla yerde kullanılan belirli bir değeri kullandığımızda ortaya çıkar. Aşağıdaki kodu ele alalım, inceleyelim:

function GetWidgetType(aWidget: TWidget): integer; 
begin 
  if aWidget.Status = 'Working' then 
  begin 
    Result := 1; 
  end 
  else 
  begin 
    if aWidget.Status = 'Broken' then 
    begin 
      Result := 2; 
    end 
    else 
    begin 
      if aWidget.State = 'Missing' then 
      begin 
        Result := 3; 
      end 
      else 
      begin 
        Result := 0; 
      end; 
    end; 
  end; 
end; 

Yukarıdaki kodu kullanmak istiyorsanız GetWidgetType işlevine ait sonuç kodunun anlamını bilmeniz gerekir. Sonuç türlerinden birini değiştirirseniz veya yenisini eklerseniz, bu işlevi kullanan kodu, kullanıldığı her yerde değiştirmeniz gerekir. Bu değişikliği yapmak için her sonuç kodunun anlamını bilmeniz gerekir. Buradaki bariz çözüm, sonuç kodu için sabit adlar veya daha iyisi, sonuç kodlarını tanımlayan numaralandırılmış bir tür kullanacak şekilde kodu yeniden düzenlemektir. Bu, sizin connascence'nizi, arzu edilen bir sonuç olarak, Anlam Connascence'sinden İsim Connascence'sine doğru azaltır. Unutmayın, daha yüksek bir düzeyden daha düşük bir connascence düzeyine doğru yeniden düzenleme (refactoring) yaptığınızda, bağlaşmayı azaltmış ve dolayısıyla kodunuzu geliştirmiş olursunuz. Anlam Uzlaşmasının bir başka örneği de sıfırın bir sinyal olarak kullanılmasıdır. Geliştiriciler genellikle nil kelimesini "değer yok" veya "Üzgünüm, bunu yapamadım/bulamadım/tamamlayamadım" anlamında kullanırlar ve kodunuzun bunu halletmesi gerekir. Daha sonraki bölümlerde göreceğimiz gibi, Anlam Connascence'si yoluyla bağlaşma yarattığı için nil'in bu kullanımından kaçınılmalıdır.

Konum Connascence'si 

Konum Connascence'si, iki farklı yerdeki kodun nesnelerin konumu üzerinde anlaşmaya varması gerektiğinde ortaya çıkar. Bu en yaygın olarak, bir metodun parametre listesindeki parametrelerin sırasının bu sırayı korumak için gerekli olduğu parametre listelerinde meydana gelir. Mevcut bir parametre listesinin ortasına bir parametre eklerseniz, bu yöntemin tüm kullanımlarında yeni parametrenin doğru konuma eklenmesi gerekir.

Bazı diller, parametrelerinizi herhangi bir sıraya dahil edilebilecek şekilde adlandırmanıza izin verir, ancak Delphi buna izin vermez. Bu nedenle Delphi kodu yazarken Konum Connascence'si kullanarak kodu bağlaştırmak gerekir.

Artık, herhangi bir rutindeki parametre sayısını sınırlayarak Konum Connascence derecesi azaltılabilir. Konum Connascence'sini sınırlamak için parametre listesini tek bir türe indirgeyebilir, böylece Konum Connascence'sinden Tür Connascence'sine geçiş yapmış olursunuz. Tip Connascence'si daha zayıf bir bağlaşımdır ve bu, yapmaya çalışmanız gereken bir şeydir.

İşte bir örnek. Bu rutini ele alalım:

procedure TUserManager.AddUser(aFirstName: string; 
       aLastName: string; aAge: integer; aBirthdate: TDateTime;           aAddress: TAddress; aPrivileges: TPrivileges); 

AddUser'ı kullanmak için tüm parametreleri tam olarak doğru konuma aldığınızdan emin olmalısınız. Ortaya bir parametre eklerseniz AddUser kullanımının bu yeni değeri tam olarak doğru yere koyduğundan emin olmanız gerekir.

Bu Konum Connascence örneğini, bunun yerine Tip Connascence'si kullanacak şekilde yeniden düzenleyerek (refactoring) azaltabiliriz. Örneğin:

type 
  TUserRecord = record 
    FirstName: string; 
    LastName: string; 
    Age: integer; 
    Birthday: TDateTime; 
    Address: TAddress; 
    Privileges: TPrivileges; 
  end; 

procedure TUserManager.AddUser(aUser: TUserRecord);

Artık bir tür oluşturarak ve AddUser prosedürünün uzun bir parametre listesinin konumu yerine parametrenin türüne bağlı olmasını sağlayarak bağlaşımı azalttık. Bağlaşımın gücünü azaltarak kodu geliştirdik. Bu tekniğe aynı zamanda “Parametre Nesnesi” de denir. (Bkz.Parametre Nesnesi)

Algoritmanın Connascence'si

Algoritma Connascence'si, iki modülün birlikte çalışabilmesi için belirli bir algoritma üzerinde anlaşması gerektiğinde ortaya çıkar.

Delphi istemcisi tarafından kullanılacak C# tabanlı API'ye sahip bir sisteminiz olduğunu hayal edin. Bu iki modül arasında gönderilen bilgiler hassastır ve şifrelenmesi gerekir. Böylece, bu iki modül Algoritma Connascence'si ile birleştirilir çünkü her ikisinin de kullanılacak şifreleme algoritması üzerinde anlaşması gerekir. Gönderici şifreleme algoritmasını değiştirirse, alıcının da aynı algoritmaya geçmesi gerekir.

Algoritmanın Connascence'sini (Uzlaşısını) azaltmak zordur, çünkü çoğu zaman yüksek derecede bir yerelliğe sahiptir (yani, bağlaşım çok uzakta gerçekleşir). Bir çözüm, algoritmanın bulunduğu tek yer haline gelen tek bir modül oluşturmak ve ardından her iki tüketen modülün de bu tek modülü kullanmasını sağlamak olabilir.

Dinamik Connascence'ler

Sonraki dört Connascence düzeyinin "dinamik" olduğu söylenir çünkü bunlar yalnızca kodunuzu çalıştırarak keşfedilebilir. Bu seviyeler statik olanlardan daha güçlüdür çünkü kendilerini yalnızca çalışma zamanında ortaya çıkarırlar, bu da onları tespit etmeyi ve çoğu zaman düzeltmeyi zorlaştırır.

Yürütme(execution) Connascence'si

Yürütme Uyumu, sistemin doğru olması için kodun yürütme sırasının gerekli olduğu durumlarda ortaya çıkar. Genellikle "Geçici Bağlaşım" olarak anılır. 

Yukarıdaki kodu kullanan bir örnek:

UserRecord.FirstName := 'Alicia'; 
UserRecord.LastName := 'Florrick'; 
UserRecord.Age = 47; 
UserManager.AddUser(UserRecord); 
UserRecord.Birthday := EncodeDate(1968, 12, 3);

Bu kod, kullanıcı eklendikten sonra Doğum Günü değerini ekler. Bu açıkça işe yaramayacak. Açıkçası, kodu incelediğinizde bu fark edilebilir, ancak fark edilmesi daha zor olan daha karmaşık bir senaryo hayal edebilirsiniz. Bu koda göz atalım:

SprocketProcesser.AddSprocket(SomeSprocket); 
SprocketProcessor.ValidateSprocket; 

Bu iki ifadenin sırası önemli mi? Dişlinin eklenmesi ve ardından doğrulanması mı gerekiyor, yoksa eklenmeden önce doğrulanması mı gerekiyor? Bunu söylemek zor ve sistemde iyi bilgi sahibi olmayan biri bunları yanlış sıraya koyma hatasına düşebilir. Bu, Yürütme Connascense'sidir.

İşte başka bir örnek. Mesajları tutan bir kuyruk hayal edin. İlk mesajda “Listeyi başlat” yazıyor. Daha sonra sonraki iki mesajın içinde listeye öğe eklemek için liste öğeleri bulunur. Ardından son olarak kuyrukta “Listeyi sonlandır” yazan bir mesaj bulunur. Öğeleri kuyruktan çeken tek bir çalışan iş parçacığınız varsa, bu harika çalışır. Tüm eşyalar sırayla çıkarılacaktır. Ancak, öğeleri kuyruktan çeken birden fazla iş parçacığınız varsa ve iş parçacıklarından biri diğerlerinden biraz daha hızlı çalıştıysa ve sonra, son "İşte başka bir öğe" mesajı işlenmeden önce "Listeyi sonlandır" mesajını kuyruktan kaldırdı mı? Bu kötü olurdu. Bu aynı zamanda Connascence of Execution'ın neden olduğu bir hata olacaktır.

Zamanlama Connascence'si

Zamanlama Connascence'si, yürütme zamanlamasının uygulamanın ürettiği çıktıda bir fark yaratması durumunda meydana gelir. Bunun en belirgin örneği, iki iş parçacığının aynı kaynağı takip ettiği ve iş parçacıklarından yalnızca birinin yarışı kazanabildiği iş parçacıklı bir yarış durumudur. Zamanlamanın Connascence'ını bulmak ve teşhis etmek oldukça zordur ve kendisini tahmin edilemeyecek şekillerde ortaya çıkarabilir.

Değer Connascence'si

Değer Connascence'si, çeşitli değerlerin modüller arasında uygun şekilde koordine edilmesi gerektiğinde ortaya çıkar. Örneğin, şuna benzeyen bir birim testiniz olduğunu hayal edin:

[Test] 
procedure TestCheckoutValue; 
var 
  PriceScanner: IPriceScanner; 
begin 
  PriceScanner := TPriceScanner.Create; 
  PriceScanner.Scan('Frosted Sugar Bombs'); 
  Assert.Equals(50, PriceScanner.CurrentBalance); 
end;

Bu yüzden testi yazdık. Şimdi, Test Odaklı Geliştirme ruhuna uygun olarak, testin mümkün olduğunca kolay ve basit bir şekilde geçmesini sağlayacağım.

procedure TPriceScanner.Scan(aItem: string);
begin
  CurrentBalance := 50;
end;

Artık TPriceScanner ile testimiz arasında sıkı bir bağlaşım var. Açıkçası Connascense of Name'e sahibiz çünkü her iki sınıf da CurrentBalance ismine dayanıyor. Ancak bu nispeten düşük bir seviyedir ve tamamen kabul edilebilirdir. Tip Connascence'ımız var, çünkü her ikisinin de TPriceScanner türü üzerinde anlaşması gerekiyor, ancak yine de bu iyi huylu. Anlam Uyumuna sahibiz, çünkü her iki rutinin de 50 sayısına sabit kodlanmış bir bağımlılığı var. Bunun yeniden düzenlenmesi gerekiyor. Ancak asıl sorun, her iki sınıfın da Buzlu Şeker Bombalarının fiyatını, yani “Değerini” bilmesi nedeniyle ortaya çıkan Değer Connascence'sidir. Fiyat değişirse bizim çok basit testimiz bile bozulur.

Çözüm, daha düşük bir Connascence düzeyine yeniden düzenleme (refactoring) yapmaktır. Yapabileceğiniz ilk şey, Buzlu Şeker Bombalarının fiyatı (değer) bilgisinin yalnızca tek bir yerde muhafaza edilmesini sağlayacak şekilde yeniden düzenleme yapmaktır:

procedure TPriceScanner.Scan(aItem: string; aPrice: integer);
begin CurrentBalance := aPrice; end;

ve şimdi testimiz aşağıdaki gibi okunabilir:

[Test]
procedure TestCheckoutValue; var PriceScanner: IPriceScanner; begin PriceScanner := TPriceScanner.Create; PriceScanner.Scan('Frosted Sugar Bombs', 50); Assert.Equals(50, PriceScanner.CurrentBalance); end;

Ve artık iki modül arasında Değer Connascence'miz yok ve testimiz hâlâ başarılı. Harika.

Kimlik Connascence'si

Kimlik Uyumu, iki bileşenin aynı nesneye gönderme yapması gerektiğinde ortaya çıkar. İki modül aynı şeye atıfta bulunuyorsa ve ardından biri bu referansı değiştirirse, diğer nesnenin de aynı referansa değişmesi gerekir. Genellikle incelikli ve tespit edilmesi zor bir connascence şeklidir. Sonuç olarak bu, connascence'nin en karmaşık biçimidir.

Aşağıdaki kodu ele alalım:

program Identity;
{$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type TReportInfo = class private FReportStuff: string; procedure SetReportStuff(const Value: string); public property ReportStuff: string read FReportStuff write SetReportStuff; end; procedure TReportInfo.SetReportStuff(const Value: string); begin FReportStuff := Value; end;
type TInventoryReport = class
private FReportInfo: TReportInfo; public constructor Create(aReportInfo: TReportInfo); property ReportInfo: TReportInfo read FReportInfo write FReportInfo; end;
TSalesReport = class private FReportInfo: TReportInfo; public constructor Create(aReportInfo: TReportInfo); property ReportInfo: TReportInfo read FReportInfo write FReportInfo; end;
constructor TInventoryReport.Create(aReportInfo: TReportInfo); begin FReportInfo := aReportInfo; end; constructor TSalesReport.Create(aReportInfo: TReportInfo); begin FReportInfo := aReportInfo; end; var ReportInfo: TReportInfo; NewReportInfo: TReportInfo; InventoryReport: TInventoryReport; SalesReport: TSalesReport; begin try ReportInfo := TReportInfo.Create; InventoryReport := TInventoryReport.Create(ReportInfo); SalesReport := TSalesReport.Create(ReportInfo); try // Do Stuff with reports NewReportInfo := TReportInfo.Create; try InventoryReport.ReportInfo := NewReportInfo;        // Do stuff with report        // But the reports now point to different ReportInfos.       // This is Connascence of Identity      finally       NewReportInfo.Free;      end;   finally      ReportInfo.Free;      InventoryReport.Free;      SalesReport.Free;   end;   except
    on E: Exception do     Writeln(E.ClassName, ': ', E.Message);   end; end.

Burada iki raporumuz var: bir envanter raporu ve bir satış raporu. Etki alanı, iki raporun her zaman aynı TReportInfo örneğine başvurmasını gerektirir. Ancak yukarıda da görebileceğiniz gibi raporlama sürecinin ortasında Envanter Raporu yeni bir ReportInfo örneğine kavuşuyor. Bu sorun değil, ancak Satış Raporunun da bu yeni TReportInfo örneğine atıfta bulunması gerekiyor. Başka bir deyişle, bir rapordaki referansı değiştirirseniz diğer raporun da aynı referansla değişmesi gerekir. Buna Kimlik Connascence'si denir, çünkü sistemin doğru çalışmaya devam etmesi için iki sınıfın da referanslarının kimliğini değiştirmesi gerekir.

Connascence Hakkında Ne Yapmalı?

Artık dokuz Connascence Düzeyini tanımladığımıza göre, kodumuzdaki bağlaşım konusunda ne yapmalıyız?

Bir miktar bağlaşımın gerçekleşmesi gerekirken, connascence'nizi mümkün olan en düşük seviyede tutmaya çalışmalısınız. Yani kodunuzdaki Connascence Derecesini azaltmalısınız. Çok temiz bir uygulama genellikle İsim ve Tür Connascence'sine sahip olacak ve Anlam ve Konum Connascence'sini mümkün olduğu kadar sınırlamaya çalışacaktır. Diğer tüm Connascence türleri gerçekten yeniden düzenlenmelidir.

Ayrıca kodunuzdaki Connascence Yerelliğini de artırmalısınız. Kodunuzdaki tüm tanımlayıcıların kapsamını azaltmak için çalışmalısınız. Bir türün kapsamını mümkün olduğu kadar düşük bir kapsam ile sınırlandırmalısınız. Birbirine ait olan şeyler bir arada tutulmalı, ait olmadıkları yerlere  gösterilmemelidir. DRY ilkesi – “Don't Repeat Yourself-Kendini Tekrarlama” – artan yerelliğin bir örneğidir. Tek Sorumluluk İlkesi (SRP-Single Responsibility Principle) de öyle.

Son olarak istikrarı tercih etmelisiniz. Connascence aslında sadece değişim ihtiyacının ölçüsüdür ve bir şeyi ne kadar az değiştirmeniz gerekiyorsa, sıkı bağlaşımın bir sonucu olarak hatalar o kadar az sıklıkla ortaya çıkar. Kararlı olan şeylerin bağlaşım hatalarına neden olma olasılığı çok daha düşük olacaktır

Sonuç

Hepimiz sıkı bağlaşımın kötü olduğu konusunda hemfikiriz (en azından öyle umuyoruz!). Ancak bağlaşım kavramı genellikle oldukça kötü tanımlanmıştır. Umarız bu kritik (bir yerde bir şey değişirse, başka bir yerde başka bir şeyin değişmesi gerektiği fikri) connascence'nin ne olduğu hakkında biraz daha spesifik konuşmanıza olanak tanır. Ayrıca, Connascence Düzeylerinin, yüksek düzeyde bağlaşıma sahip kodu bulmanıza ve bağlaşımınızı azaltmak için bu alanları yeniden düzenlemenize olanak sağlayacağını umuyorum. Kodunuzdaki genel Connascence düzeyini azaltmaya çalışırsanız, daha temiz ve bakımı daha kolay bir kod tabanına sahip olursunuz.