Structural — Yapısal Desenler
Adapter
Adaptör (Uyarlayıcı) Deseni
Amaç: Birbiriyle uyumsuz arayüzlere sahip sınıfların, aralarına bir çevirici koyarak birlikte çalışabilmesini sağlamak.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Türkiye'den İngiltere'ye tatile gittiniz. Telefonunuzun şarj aleti iki yuvarlak uçlu, ama oteldeki prizler üç köşeli. Ne telefonunuzu değiştirirsiniz ne de otelin duvarındaki prizi. Bunun yerine küçük bir priz adaptörü takarsınız.
Adaptör bir tarafta İngiliz prizine uyar, diğer tarafta sizin fişinizi kabul eder. İki taraf da değişmeden birlikte çalışır.
Bir de tercüman var
Türkçe konuşan bir iş insanı ile yalnızca Japonca konuşan bir müşteri arasında tercüman durur. Tercüman iki tarafın dilini de bilir ve mesajları çevirir. Kimse yeni bir dil öğrenmek zorunda kalmaz.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
Adapter, var olan bir sınıfın (Adaptee) arayüzünü, istemcinin beklediği başka bir arayüze (Target) dönüştürür. Genellikle değiştiremediğiniz kodlarla (üçüncü parti kütüphaneler, eski sistemler, dış servisler) çalışırken kullanılır.
Katılımcılar
- Target: İstemcinin beklediği arayüz (
IPaymentProcessor). - Adaptee: Kullanmak istediğimiz ama arayüzü uymayan sınıf (
LegacyBankApi). - Adapter: Target'ı uygular, içeride Adaptee'yi çağırır ve veriyi dönüştürür.
- Client: Yalnızca Target arayüzünü bilir.
İki türü vardır
- Object Adapter (önerilen): Adaptee'yi bir alan olarak tutar (composition).
- Class Adapter: Adaptee'den kalıtım alır. C# çoklu sınıf kalıtımını desteklemediği için daha az kullanılır.
Avantajlar
- Single Responsibility: Dönüştürme mantığı iş mantığından ayrılır.
- Open/Closed: Yeni bir dış servis için yeni bir adaptör yazılır, istemci değişmez.
- Eski (legacy) kodu yeniden yazmadan yeni sisteme entegre etmeyi sağlar.
Dezavantajlar
- Ek bir katman getirir; bazen Adaptee'yi doğrudan değiştirmek daha basittir.
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Uygulamamız IPaymentProcessor arayüzüyle ödeme alıyor. Yıllar önce yazılmış, değiştiremediğimiz bir banka kütüphanesini de sisteme dahil etmemiz gerekiyor; ama bu kütüphane tutarı kuruş cinsinden, farklı metot adlarıyla istiyor ve sonucu bir kodla döndürüyor.
Adım 1 Target: uygulamamızın beklediği arayüz
public interface IPaymentProcessor
{
bool Pay(string customerId, decimal amountTl);
}
public class ModernPaymentService : IPaymentProcessor
{
public bool Pay(string customerId, decimal amountTl)
{
Console.WriteLine($"[Modern] {customerId} için {amountTl:0.00} TL tahsil edildi.");
return true;
}
}Adım 2 Adaptee: değiştiremediğimiz eski kütüphane
Farklı metot adı, farklı parametre tipleri, farklı dönüş değeri.
public class LegacyBankApi
{
// Tutar kuruş cinsinden, sonuç "00" ise başarılı.
public string SendMoney(long amountInKurus, int accountNo)
{
Console.WriteLine($"[Legacy] Hesap #{accountNo} -> {amountInKurus} kuruş gönderildi.");
return "00";
}
}Adım 3 Adapter: çeviriyi yapan sınıf
IPaymentProcessor'ı uygular, içeride LegacyBankApi'yi kullanır. TL'yi kuruşa, müşteri kimliğini hesap numarasına, sonuç kodunu bool'a çevirir.
public class LegacyBankAdapter : IPaymentProcessor
{
private readonly LegacyBankApi _legacy;
public LegacyBankAdapter(LegacyBankApi legacy) => _legacy = legacy;
public bool Pay(string customerId, decimal amountTl)
{
long kurus = (long)(amountTl * 100);
int accountNo = int.Parse(customerId.Replace("CUST-", ""));
string resultCode = _legacy.SendMoney(kurus, accountNo);
return resultCode == "00";
}
}Adım 4 İstemci: yalnızca hedef arayüzü tanır
public class CheckoutService
{
private readonly IPaymentProcessor _processor;
public CheckoutService(IPaymentProcessor processor) => _processor = processor;
public void Complete(string customerId, decimal total)
{
bool ok = _processor.Pay(customerId, total);
Console.WriteLine(ok ? " -> Sipariş tamamlandı." : " -> Ödeme başarısız!");
}
}Adım 5 Kullanım
Tüm parçaları bir araya getiren istemci (client) kodu:
var modern = new CheckoutService(new ModernPaymentService());
modern.Complete("CUST-1001", 249.90m);
var legacy = new CheckoutService(new LegacyBankAdapter(new LegacyBankApi()));
legacy.Complete("CUST-2002", 249.90m);Konsol çıktısı
[Modern] CUST-1001 için 249,90 TL tahsil edildi. -> Sipariş tamamlandı. [Legacy] Hesap #2002 -> 24990 kuruş gönderildi. -> Sipariş tamamlandı.
Kodda dikkat edilmesi gerekenler
CheckoutServicehangi bankayla çalıştığını bilmiyor. Eski kütüphane, sanki baştan beriIPaymentProcessoruyguluyormuş gibi kullanıldı.LegacyBankApisınıfında tek satır değişmedi.