DELPHI OOP PRATİKLERİ
Bir Delphi projesi açıp altı ay sonra geri döndüğünüzde manzara çoğu zaman aynıdır: tek bir form unit'i iki bin satıra şişmiş, private olması gereken alanlar form dışından serbestçe okunup yazılıyor, Create ve Destroy çiftleri arasında bellek sızıntıları, her yeni ihtiyaçta if blokları içine sıkıştırılmış davranış kuralları. Delphi sağlam bir nesne yönelimli dil olmasına rağmen, dil ne yapacağınızı zorla kabul ettirmez — disiplin geliştiriciye aittir. Bu yazı, Delphi'de class tasarımını sürdürülebilir tutan pratikleri toplar: hangi erişim belirtecini ne zaman seçeceksiniz, property neden tercih edilir, kalıtım nerede tuzaktır, interface ile composition'ı nasıl birleştirirsiniz.
Görünürlük Disiplini ve Erişim Belirteçleri
Delphi dört temel görünürlük seviyesi sunar: private, protected, public ve published. Çoğu geliştirici bunu üç katmana indirgeyip her şeyi public bırakır — sonuçta bir sınıfın iç durumu dışarıdan keyfi şekilde değiştirilebilir hale gelir.
Pratikte iyi bir Delphi sınıfı şu sırayla yazılır: alanlar (F öneki ile) önce strict private, davranışı dışarı açan metodlar public, kalıtımla genişletilecek noktalar protected. private ile strict private arasındaki farkı bilmek gerekir: klasik private aynı unit içindeki diğer sınıflara erişime izin verir, strict private ise bu erişimi kapatır. Gerçekten kapalı bir API hedefliyorsanız strict private tercih edilmelidir.
type
TUser = class
strict private
FName: string;
FAge: Integer;
public
constructor Create(const AName: string; AAge: Integer);
property Name: string read FName;
property Age: Integer read FAge;
end;Burada FName ve FAge dışarıya kapalıdır; ad ve yaş sadece constructor üzerinden atanır, okuma yine sadece read-only property üzerinden mümkündür. Bu basit alışkanlık, ileride invalid bir kullanıcı nesnesinin sisteme sızmasını engeller.
Constructor, Destructor ve Yaşam Döngüsü
Delphi'de bellek yönetimi otomatik değildir — class instance'ları Create ile ayrılır, Destroy ile serbest bırakılır. En sık yapılan hata, override edilen Destroy içinde inherited çağrısını unutmaktır. Bu durumda parent sınıfın temizlik kodu çalışmaz ve sızıntı oluşur.
destructor TOrder.Destroy;
begin
FItems.Free;
inherited; // mutlaka çağrılmalı
end;Constructor'da yaratılan her nesne, destructor'da serbest bırakılmalıdır. FreeAndNil tercih edilmelidir; nesneyi serbest bıraktıktan sonra referansı nil yapar. Bu sayede tekrar erişim denemeleri AV (access violation) yerine kontrollü Assigned kontrolüne dönüşür.
Bir başka kritik nokta: nesne yaratımı ile birlikte mutlaka try..finally bloğu kurulmalıdır. Constructor başarıyla döndüyse ama sonraki satırda exception oluşursa, finally bloğu olmadan o nesne bellekte kalır.
var
User: TUser;
begin
User := TUser.Create('Ada', 32);
try
SaveToDatabase(User);
finally
User.Free;
end;
end;Properties: Saha Değil Sözleşme
Doğrudan public field tanımlamak hızlıdır ama bir kez yapıldığında geri dönüşü zordur. Property kullanmak, dışa sunulan arayüzü değiştirmeden iç davranışı kontrol altında tutmanızı sağlar. Bugün FAge sadece bir alan okuyor olabilir; yarın age değerinin negatif olup olmadığını kontrol etmek gerekirse, property'nin setter'ına bir satır ekleyerek bunu yaparsınız. Public field olsaydı tüm dışarıdaki kodu değiştirmeniz gerekirdi.

Property kullanırken birkaç pratik kural işe yarar:
- Read-only başlayın: Yeni bir property yazarken önce sadece read tarafını koyun. Yazma ihtiyacı doğduğunda eklersiniz. Default olarak yazılabilir bırakmak gereksiz kapı açar.
- Setter'ı boş bırakmayın: Setter'da değişim kontrolü yapın — yeni değer eskiyle aynıysa hiçbir şey yapmayın. Bu, property change event'leri zinciri başlatan UI bileşenlerinde performansı korur.
- Published, RTTI içindir:
publishedsadece tasarım zamanı serileştirme, Object Inspector ve RTTI kullanılacak class'larda anlamlıdır. Pure logic sınıflarında published yazmak yanlıştır.
Kalıtım Tuzakları ve Kompozisyon
Delphi VCL'in kendisi derin bir kalıtım ağacına dayanır — TObject, TPersistent, TComponent, TControl, TWinControl. Bu yapı işe yarar ama yanlış yerde kalıtım, sürdürülemez sistemler doğurur. Genel kural: bir sınıf başka bir şeydir diyebiliyorsanız kalıtım, başka bir şeye sahip diyorsanız kompozisyondur. Dilin nesne yönelimli yapısının kavramsal temelleri ve sınıf modeli için sınıf ve nesne modelinin teknik referansını incelemek, hangi mekanizmanın ne için tasarlandığını netleştirir.
Klasik yanlış örnek: TInvoice = class(TPDFExporter). Fatura bir PDF dışa aktarıcı değildir; faturanın bir PDF dışa aktarıcı kullanma ihtiyacı vardır. Doğrusu kompozisyondur:
type
TInvoice = class
strict private
FExporter: IPDFExporter;
public
constructor Create(const AExporter: IPDFExporter);
procedure ExportPdf(const APath: string);
end;Bu yaklaşım iki avantaj getirir. Birincisi, IPDFExporter farklı uygulamalarla değiştirilebilir — test ortamında bir mock, prod ortamında gerçek bir PDF kütüphanesi. İkincisi, TInvoice'un sorumluluğu fatura mantığı ile sınırlı kalır; PDF üretimi başka bir nesneye delege edilir.
Interface'ler ile Bağımlılık Soyutlama
Delphi'de interface, sınıf hiyerarşisinden bağımsız bir sözleşme tanımlar. Bir interface tanımlandığında, ona uyan herhangi bir sınıf — birbiriyle hiç akrabalığı olmasa bile — o sözleşmeyi yerine getirebilir.
type
IRepository<T> = interface
['{B8F1C5D2-3A4F-4E2C-9D7A-12345ABCDE12}']
function GetById(AId: Integer): T;
procedure Save(AItem: T);
end;GUID interface tanımında zorunluluk gibi görünmez ama RTTI, COM uyumluluğu ve Supports fonksiyonu için gereklidir. IDE'de Ctrl+Shift+G ile yeni GUID üretilir.
Interface kullanan bir sınıfı TInterfacedObject'ten türetirseniz referans sayma otomatik devreye girer; Free çağırmak yerine referans bittiğinde nesne kendi kendini serbest bırakır. Bu güçlü bir araçtır ama VCL bileşenleri ile karıştırırsanız çift serbest bırakma (double-free) sorunu yaşarsınız. Component sahipliği zincirine giren nesneleri interface ile yönetmek için TSingletonImplementation kullanmak gerekir.
SOLID Prensiplerinin Delphi'ye Uyarlanması
SOLID prensipleri Delphi'ye de uyar; sadece Pascal sözdizimine taşınmıştır. Pratikte en hızlı kazanç veren ikisi:
- Single Responsibility: Bir sınıfın değişme nedeni bir tane olmalı. Form unit'inde hem veritabanı erişimi, hem business logic, hem UI bağlamı olan kodu üç ayrı sınıfa bölün — DAO katmanı, service katmanı, view katmanı.
- Dependency Inversion: Üst seviye sınıflar somut sınıflara değil, interface'lere bağımlı olmalı. Bir TOrderService doğrudan TSqlOrderRepository'e değil, IOrderRepository'ye bağlanmalı. Test edilebilirlik buradan doğar.
Delphi'nin nesne yönelimli özelliklerini sistematik şekilde öğrenmek isteyenler için Delphi eğitimi bu pratikleri proje senaryoları üzerinden uygulamalı işler.
Yaygın Anti-Pattern'ler
Delphi projelerinde sıkça karşılaşılan ve refactor öncesi öncelikle ele alınması gereken kalıplar:
- Form üzerinden iletişim: İki form'un birbirine erişerek property okuması veya method çağırması. Doğrusu, bir service ya da event tabanlı mesajlaşma katmanıdır.
- Global TDataModule bağımlılığı: Tüm sınıfların DM1.Query1'i çağırması. DataModule referansını parametre ile geçirin veya bir interface üzerinden enjekte edin.
- God class: Üç binden fazla satır içeren, onlarca metod barındıran tek sınıf. Sorumluluğu küçük parçalara bölün; her parça kendi unit'inde test edilebilir olsun.
- If-else davranış zinciri: Müşteri tipine göre dallanan onlarca
ifbloğu. Strategy pattern uygulayın; her müşteri tipi kendi sınıfında, ortak interface'i yerine getirir.
Bu pratiklerin tümünü ilk gün uygulamak zorunda değilsiniz. Yeni yazdığınız her sınıfta bir tane uygulayarak başlayın — strict private alanlar, sonra property'ler, sonra interface tabanlı bağımlılıklar. Üç ay içinde proje kalitesinde gözle görülür bir fark oluşur.



