Eski yazılarımızda Zengin Yedek Kulubesi adı altında üzerine gittiğimiz bir konu vardı. Amacımız tek programla birden fazla veritabanını desteklemekti. Bunun için çeşitli yöntemler üzerinde durmuş dbexpress ile de ufak bir örnek yapmıştık.
Bu konunun ardından Interface'ler konusuna girip birkaç yazıda bu konuyla ilgili yazmıştık. Şimdi ise interfaceler sayesinde zengin yedek kulubesi oluşturmanın bir başka yolunu -bence en iyisi- göreceğiz.
Interfacelerin temel varoluş şeklini hatırlarsak neyi nasıl yapacağımızı daha kolay anlama şansımız olabilir. Interfaceler sadece
"Ne" sorusuna cevap veriyolarlardı.
"Nasıl" sorusuna ise cevap vermiyorlardı. Bizde bu nimetten yararlanarak arabirim kodu üzerinde minumum oynama yaparak programımıza birden çok veritabanı ile çalışma esnekliğini kazandırmış olacaz.
Toparlarsak; mantık şu şekilde olacak.
//Yeni Kayıt Ekleme İşlemi
Interface.Ad := 'Ahmet'
Interface.Soyad:= 'Çakar'
Interface.Ekle;
ShowMessage('kayıt eklendi');
Peki burdaki interface neyin interfaci veya neye atıfta bulunuyor. Interface.Ekle dediğimiz zaman hangi sınıfın ekle metodu çalışıyor?
"veritabanı olarak paradox kullanmak istiyorum" düğmesine tıklandığında çalışan kod
Interface :=TParadox.Create(Database);
Eğer programın paradox veritabanını kullanmasını istiyorsak yukarıdaki gibi Interface nesnemize TParadox sınıfına ait bir nesneyi atıyoruz eğer programın Access veritabanını kullanmasını isteseydik bu sefer Interface :=TAccess.Create(Database); gibi bir satır yazmamız gerekecekti. Aynı şekilde programın Oracle kullanmasını isteseydik Interface :=TOracle.Create(Database); yazmamız kafi olacaktı.
Peki bunun avantajı ne olacak ?
Biz hangi veritabanını kullanırsak kullanalım yukarıda yazdığımız "Yeni Kayıt Ekleme İşlemi" metodundaki kodlarımızın hiçbirisi etkilenmeyecek. Oradaki kodlar Oracle seçmisek Oracle'a göre, Access seçmişsek Access'a göre , Firebird seçmiçsek Firebird'e göre sorunsuz bir şekilde çalışacaktır.
Dezavantajı Nedir?
Çok fazla kod yazmanız gerekecek. Yani kaç tane veritabanına destek vermek istiyorsanız o kadar Interface.Ekle; metodu yazmak zorunda kalacaksınız. Diyelim ki 3 tane veritabanına destek vermek istiyorsunuz. Access, Firebird ve Oracle. Bu en az 3 tane sınıfınız olacak demektir ve her sınıfın kendi Ekle metodunu da yazmanız gerekiyor demektir. Gerçi bu sınıfların içerisinde dbexpress, ado gibi teknolojileri kullanıp tek bir ekle metoduylada bu iş çözülebilir gibi olsada bana göre en güvenli ve en bağımsız çözüm yolu herbirini ayrı ayrı yazmaktır. Hepsini ayrı ayrı yapmanız size tam bir bağımsızlık kazandırır.
Burda yapacağımız örneğin içeriği basitçe şu şekilde olacak. Örneğimiz iki veritabanını destekleyecek. Paradox ve Access. Üzerinde işlem yapacağımız tablo ise çok basit bir tablo. Tablonun alanları aşağıdaki gibi :
KISILER TABLOSU :
ID: Integer
AD: Karakter
SOYAD : Karakter
Bu tabloyu hem paradoxta hem de access te oluşturduktan sonra bu tablolara erişecek iki sınıf tanımladım.
TPrdxKisiler = Paradoxta oluşturulmuş olan kişiler tablosu üzerinde işlem yapacak olan sınıfımız
TAccessKisiler = Accessta oluşturulmuş olan kişiler tablosu üzerinde işlem yapacak olan sınıfımız
İki nesneninde çok fazla ortak yönü olduğundan bu nesleri bir ata nesneden türetmeyi tekrarlanan kod sayısını azaltmak açısından uygun gördüm ve bu nedenle her iki nesneyi de TAtaKisiler adlı bir sınıftan türettim. Interfacei uygulayan nesnede zaten bu nesne....
Kısaca dördü(Interface, Ata nesne ve diğer iki türetilmiş nesne) arasındaki ilişkiyi şu şekilde özetleyebiliriz.

Diğer bir önemli hususta bu uygulamanın DUnit ile birim testlerinin yapılıyor olması. Yani sınıflarımızda yer alan Ekle, Düzenle, Sil gibi metodların doğru çalışıp çalışmadığını test eden kodlarımız var. TDD Üzerine adlı kısımda konuyla ilgili daha ayrıntılı bilgi bulabilirsiniz.
Şimdi en başa geri dönelim. Veritabanımızda 3 tane alan bulunuyor. ID, AD ve SOYAD. İlk önce Interfacein bu alanları bir şekilde karşılaması gerekiyor. Yapısı gereği içeresinde her hangi bir değişken barındıramadığından bu alanları property olarak tanımlamamız gerekiyor. Değerleri okumak ve set etmek içinde yine birer tane prosedür ve fonksiyona ihtiyacımız olacak. E tabi birde Ekle, sil ve güncelle metodlarımız olacak. Interfacein oluşturulduğu kod aşağıdaki şekilde.
IData = Interface
['{D1332F05-2543-42DB-9267-401304481BC1}']
function GetID():Integer;
Procedure SetID(Const Value : Integer);
function GetAd():String;
Procedure SetAd(Const Value : String);
function GetSoyAd():String;
Procedure SetSoyAd(Const Value : String);
property ID:Integer read GetID write SetID;
property Ad:String read GetAd write SetAd;
property Soyad:String read GetSoyad write SetSoyad;
procedure Ekle;
procedure Sil;
procedure Guncelle;
end;
Sırada bu interfaci kullanan ve asıl kullanmak istediğimiz nesnelere atalık edecek olan TAtaKisiler sınıfının tanımı...
TAtaKisiler = class(TInterfacedObject,IData)
private
fID:Integer;
fAd:String;
fSoyad:String;
procedure SetID(const Value: Integer);
function GetID: Integer;
function GetAd: String;
function GetSoyad: String;
procedure SetAd(const Value: String);
procedure SetSoyad(const Value: String);
{ Private declarations }
public
constructor Create();virtual;abstract;
property ID:Integer read GetID write SetID;
property Ad:String read GetAd write SetAd;
property Soyad:String read GetSoyad write SetSoyad;
procedure Ekle;virtual;abstract;
procedure Sil;virtual;abstract;
procedure Guncelle;virtual;abstract;
{ Public declarations }
end;
//
Bu sınıfın diğer sınıfların
(TPrdxKisiler,TAccessKisiler) ortak bazı alan ve metodlarını içerdiği için çalışma sırasında her hangi bir şekilde bir örneğinin oluşturulmasını istemiyorum çünkü kendisi tek başına her hangi bir anlam ifade etmeyen bir sınıf. Bu yüzden Create yapılandırıcısını abstract anahtarıyla soyut hale getiriyorum. (constructor Create();virtual;abstract;) Eğer bu sınıfa ait bir nesne oluşturmak isterseniz Delphi bunu kabul etmez ve oluşturmaz. Her nesnede ortak olan alan ve metodları burda tanımlıyorum fakat her metodu diğer nesneler kendilerine göre yeniden düzenlemeleri gerektiğinden metod isimlerinin sonuna abstract anahtarını eklemeyi unutmuyorum.
ve nihayetinde işte karşımızda kullanacağımız asıl sınıflardan biri....
TPrdxKisiler = class(TAtaKisiler)
private
fDatabase : TDatabase;
fQuery:TQuery;
{ Private declarations }
public
constructor Create(DataBase:TDatabase);reintroduce;
destructor Destroy; override;
procedure Ekle;override;
procedure Sil;override;
procedure Guncelle; override;
{ Public declarations }
end;
bu da bir diğeri....
TAccessKisiler = class(TAtaKisiler)
private
fAdoConnection : TAdoConnection;
fQuery:TAdoQuery;
{ Private declarations }
public
constructor Create(AdoConnection:TAdoConnection);reintroduce;
destructor Destroy; override;
procedure Ekle;override;
procedure Sil;override;
procedure Guncelle; override;
{ Public declarations }
end;
Private alanında tanımladığım nesnelerin tipine ve Create yapılandırıcısındaki parametre tipine dikkat edin.
Gövde metodlarının hepsini buraya yazmayacam ama örnek olması için sadece TPrdxKişiler sınıfının metod gövdesini buraya ekliyorum.
{ TPrdxKisiler }
constructor TPrdxKisiler.Create(DataBase: TDatabase);
begin
inherited;
fDatabase :=DataBase;
fQuery :=TQuery.Create(nil);
fQuery.DatabaseName :='SADODATA';
end;
destructor TPrdxKisiler.Destroy;
begin
FreeAndNil(fquery);
end;
procedure TPrdxKisiler.Ekle;
begin
fQuery.Close;
fQuery.SQL.Clear;
fQuery.SQL.Add('Insert Into KISILER (AD,SOYAD) '+#13+
'Values (:AD,:SOYAD)');
fQuery.Params[0].AsString := fAd;
fQuery.Params[1].AsString := fSoyad;
fQuery.ExecSQL;
end;
procedure TPrdxKisiler.Guncelle;
begin
fQuery.Close;
fQuery.SQL.Clear;
fQuery.SQL.Add('Update KISILER Set AD = :AD,'+#13+
'SOYAD = :SOYAD Where ID = :ID');
fQuery.Params[0].AsString := fAd;
fQuery.Params[1].AsString := fSoyad;
fQuery.Params[2].AsInteger := fID;
fQuery.ExecSQL;
end;
procedure TPrdxKisiler.Sil;
begin
fQuery.Close;
fQuery.SQL.Clear;
fQuery.SQL.Add('Delete From KISILER Where ID = :ID');
fQuery.Params[0].AsInteger := fID;
fQuery.ExecSQL;
end;
Sıra geldi bunları kullanmaya...
Formun üzerinde iki tane düğme var. Paradox kullanmak isteyen birisi "Paradox kullanmak istiyorum" adlı düğmeyi, Access kullanmak isteyen birisi ise "Access Kullanmak istiyorum" adlı düğmeyi tıklıyor. Bunun yanında kayıt silmek , güncellemek ve eklemek içinde 3 adet düğme bulunuyor. Tabi birde verilerin girilebileceği editler. Test etmek amacıyla sol tarafa iki adet grid yerleştirdim. Düğmlere tıkladıktan sonra bu gridlerden işlemlerin başarılı bir şekilde olup olmadığını burdan kontrol ediyorum.

Interfacei formun public bir alanında Data:IData şeklinde tanımladıktan sonra ilgili düğmelerde interface uygun değerleri aktarıyorum.
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
Data :=TPrdxKisiler.Create(Database1);
end;
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
Data :=TAccessKisiler.Create(ADOConnection1);
end;
Ekle, sil, Düzenle düğmelerinin kodları ise şu şekilde...
procedure TForm1.Button1Click(Sender: TObject);
begin
Data.Ad := edit2.Text;
Data.Soyad:=edit3.Text;
Data.Ekle;
ShowMessage('kayıt eklendi');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Data.ID := StrToInt(edit1.Text);
Data.Ad := edit2.Text;
Data.Soyad:=edit3.Text;
Data.Guncelle;
ShowMessage('kayıt güncellendi');
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
Data.ID := StrToInt(edit1.Text);
Data.Sil;
ShowMessage('kayıt silindi');
end;
Programa yeni bir veritabanı desteği eklemek istediğinizde yapmanız gereken nedir?
TPrdxKisiler gibi bir sınıf daha oluşturmak (mesela TFirebirdKisiler), programa "Firebird Kullan" başlıklı bir buton eklemek ve butonun koduna
Data :=TFirebirdKisiler.Create(IBDatabase1); yazmak. Hepsi Bu.
Aslında testler bana göre daha ilginç ama testleri şu an için buraya koymaya üşeniyorum. Kaynak kodu indirdiğiniz zaman DUnit klasörü altındaki test kodlarına bakmayı da unutmayın. Düşüncelerinizi de burda belirtirseniz çok sevinirim.
//edit: test kodlarını yorum bölümüne ekledim
ÖRNEK UYGULAMAYI İNDİRMEK İÇİN BURAYA TIKLAYIN.
Bu arada RemObjects software'in
Data Abstract adlı ürününe de bir gözatın...