Structural — Yapısal Desenler
Decorator
Dekoratör (Süsleyici) Deseni
Amaç: Bir nesneye, sınıfını değiştirmeden ve kalıtım kullanmadan, çalışma zamanında katman katman yeni davranışlar eklemek.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Bir kahve dükkânındasınız. Sade bir filtre kahve sipariş ediyorsunuz: 40 TL. "İçine süt ekleyin" diyorsunuz: +10 TL. "Bir de karamel şurubu": +15 TL. "Üstüne krema": +12 TL.
Kahve hâlâ kahve; ama her eklemeyle üstüne bir katman giydirildi. Dükkânın menüsünde "sütlü-karamelli-kremalı kahve" diye ayrı bir ürün yok. Sadece bir temel ürün ve istediğiniz gibi birleştirebileceğiniz eklemeler var.
Kış giyimi
Hava soğuyunca tişörtün üstüne kazak, onun üstüne mont, yağmur yağarsa en üste yağmurluk giyersiniz. Her katman size yeni bir özellik kazandırır (sıcaklık, su geçirmezlik) ve istediğiniz zaman çıkarabilirsiniz. Siz değişmediniz, sadece giydikleriniz değişti.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
Decorator, sarmaladığı nesneyle aynı arayüzü uygulayan bir sarmalayıcıdır (wrapper). İsteği içerideki nesneye iletir ve öncesinde/sonrasında kendi davranışını ekler. Dekoratörler iç içe geçirilerek davranışlar birleştirilir.
Problem: Kalıtım patlaması
Bir veri kaynağına sıkıştırma, şifreleme ve loglama eklemek isteyelim. Kalıtımla CompressedEncryptedLoggedStream gibi her kombinasyon için ayrı sınıf gerekir. Üstelik bu kombinasyonlar derleme zamanında sabitlenir.
Katılımcılar
- Component: Ortak arayüz (
IDataSource). - ConcreteComponent: Temel nesne (
FileDataSource). - BaseDecorator: Component'i uygular ve bir Component referansı tutar.
- ConcreteDecorator: Ek davranışı uygular (
EncryptionDecoratorvb.).
.NET'ten tanıdık örnekler
Streamhiyerarşisi:new GZipStream(new BufferedStream(new FileStream(...)))- ASP.NET Core middleware zinciri ve
HttpClient'takiDelegatingHandler'lar - Scrutor kütüphanesindeki
services.Decorate<IService, CachedService>()
Avantajlar
- Davranışlar çalışma zamanında eklenip çıkarılabilir.
- Her dekoratör tek bir sorumluluğa sahiptir (Single Responsibility).
- Mevcut sınıflar değiştirilmeden genişletilir (Open/Closed).
Dezavantajlar
- Sarmalama sırası önemlidir (önce sıkıştır sonra şifrele ≠ önce şifrele sonra sıkıştır).
- Çok katmanlı yapılarda hata ayıklamak zorlaşabilir.
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Bir veri kaynağına yazılan metne isteğe bağlı olarak sıkıştırma, şifreleme ve loglama eklemek. (Örnek basit tutulmak için gerçek algoritmalar yerine görünür dönüşümler kullanıyor.)
Adım 1 Component ve temel uygulama
public interface IDataSource
{
void Write(string data);
string Read();
}
public class MemoryDataSource : IDataSource
{
private string _stored = "";
public void Write(string data) => _stored = data;
public string Read() => _stored;
}Adım 2 Temel dekoratör
Varsayılan olarak her şeyi içerideki nesneye iletir. Somut dekoratörler yalnızca değiştirmek istedikleri kısmı override eder.
public abstract class DataSourceDecorator : IDataSource
{
protected readonly IDataSource Inner;
protected DataSourceDecorator(IDataSource inner) => Inner = inner;
public virtual void Write(string data) => Inner.Write(data);
public virtual string Read() => Inner.Read();
}Adım 3 Somut dekoratörler
public class EncryptionDecorator : DataSourceDecorator
{
public EncryptionDecorator(IDataSource inner) : base(inner) { }
// Basit gösterim: metni ters çevirip Base64'e dönüştürür.
public override void Write(string data) =>
base.Write(Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes(new string(data.Reverse().ToArray()))));
public override string Read() =>
new string(System.Text.Encoding.UTF8.GetString(
Convert.FromBase64String(base.Read())).Reverse().ToArray());
}
public class CompressionDecorator : DataSourceDecorator
{
public CompressionDecorator(IDataSource inner) : base(inner) { }
// Basit gösterim: art arda gelen boşlukları tek boşluğa indirir.
public override void Write(string data) =>
base.Write(System.Text.RegularExpressions.Regex.Replace(data, " +", " "));
}
public class LoggingDecorator : DataSourceDecorator
{
public LoggingDecorator(IDataSource inner) : base(inner) { }
public override void Write(string data)
{
Console.WriteLine($" [LOG] Yazılıyor: \"{data}\"");
base.Write(data);
}
public override string Read()
{
var result = base.Read();
Console.WriteLine($" [LOG] Okundu: \"{result}\"");
return result;
}
}Adım 4 Kullanım
Tüm parçaları bir araya getiren istemci (client) kodu:
var memory = new MemoryDataSource();
// Katmanlar: Log -> Sıkıştırma -> Şifreleme -> Bellek
IDataSource source =
new LoggingDecorator(
new CompressionDecorator(
new EncryptionDecorator(memory)));
source.Write("Gizli müşteri verisi");
Console.WriteLine($"Bellekte duran ham veri : {memory.Read()}");
Console.WriteLine($"Uygulamanın okuduğu veri: {source.Read()}");
Console.WriteLine("\nSadece loglama istenirse:");
IDataSource simple = new LoggingDecorator(new MemoryDataSource());
simple.Write("Herkese açık veri");Konsol çıktısı
[LOG] Yazılıyor: "Gizli müşteri verisi" Bellekte duran ham veri : aXNpcmV2IGlyZXTFn8O8bSBpbHppRw== [LOG] Okundu: "Gizli müşteri verisi" Uygulamanın okuduğu veri: Gizli müşteri verisi Sadece loglama istenirse: [LOG] Yazılıyor: "Herkese açık veri"
Kodda dikkat edilmesi gerekenler
- Hiçbir sınıfı değiştirmeden üç özelliği istediğimiz sırayla birleştirdik.
- Bellekte veri şifreli duruyor, ama uygulama okurken katmanlar sırayla geri dönüşümü yaptığı için düz metin görüyor.
- İkinci örnekte sadece loglama kullanıldı: hangi katmanların olacağı tamamen çalışma zamanında belirleniyor.