Behavioral — Davranışsal Desenler
Template Method
Şablon Metot Deseni
Amaç: Bir algoritmanın iskeletini üst sınıfta tanımlamak, bazı adımlarının uygulanmasını ise alt sınıflara bırakmak.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Çay ve kahve yapmayı düşünün. İkisinin de tarifi aslında çok benzer:
- Suyu kaynat
- İçeceği demle (çay: demliğe çay koy / kahve: filtreden geçir)
- Bardağa dök
- Eklemeleri yap (çay: limon / kahve: süt ve şeker)
Birinci ve üçüncü adımlar her zaman aynı. Sadece ikinci ve dördüncü adımlar içeceğe göre değişiyor. Sıralama da hiç değişmiyor: bardağa dökmeden önce demlemek zorundasınız.
Ev inşaatı
Bir müteahhit firmanın ev yapım süreci sabittir: temel → duvarlar → çatı → iç tasarım. Ama müşteri ahşap ev mi, betonarme ev mi istediğine göre duvar ve çatı adımları farklı uygulanır.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
Template Method, algoritmanın adımlarını sırasıyla çağıran (genellikle virtual olmayan) bir şablon metot içeren soyut bir sınıftır. Değişen adımlar abstract (zorunlu) veya virtual (opsiyonel, hook) metotlar olarak bırakılır.
Adım türleri
- Abstract adımlar: Alt sınıf mutlaka uygulamalıdır.
- Varsayılan adımlar: Üst sınıfta uygulanmıştır, alt sınıf isterse override eder.
- Hook'lar: Boş veya basit varsayılanlı metotlar; alt sınıfa algoritmanın belirli noktalarına "kanca atma" imkânı verir (
ShouldSendEmail()gibi).
Hollywood Prensibi
"Bizi aramayın, biz sizi ararız." Alt sınıflar üst sınıfı çağırmaz; üst sınıfın şablon metodu, uygun zamanda alt sınıfın adımlarını çağırır. Bu, framework'lerin temel çalışma prensibidir.
.NET'te tanıdık örnekler
- ASP.NET Core'daki
BackgroundService.ExecuteAsync: başlatma/durdurma şablonu framework'te, siz sadece işi yazarsınız. Streamsınıfının bazı metotları,DbContext.OnModelCreating, xUnit/NUnit yaşam döngüsü metotları.
Strategy'den farkı
Template Method kalıtım ile algoritmanın bir kısmını değiştirir (derleme zamanında). Strategy composition ile algoritmanın tamamını değiştirir (çalışma zamanında).
Dezavantajlar
- Kalıtıma dayandığı için esnekliği sınırlıdır; alt sınıf üst sınıfa sıkı bağlıdır.
- Çok fazla adım ve hook, şablonu anlaşılmaz hale getirebilir.
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Farklı formatlarda (CSV, JSON) veri içe aktaran bir sistem. Okuma ve ayrıştırma formata göre değişiyor; doğrulama, kaydetme ve raporlama sırası ise her zaman aynı.
Adım 1 Soyut sınıf ve şablon metot
Import metodu algoritmanın iskeletidir ve override edilemez (virtual değil). Değişen adımlar alt sınıflara bırakılmıştır.
public abstract class DataImporter
{
// ŞABLON METOT: sıralama burada sabitlenir.
public void Import(string source)
{
Console.WriteLine($"[{GetType().Name}] içe aktarma başladı");
string raw = ReadData(source); // abstract
List<string> records = Parse(raw); // abstract
var valid = records.Where(Validate).ToList(); // varsayılan davranış
Save(valid); // ortak adım
if (ShouldNotify()) Notify(valid.Count); // hook
Console.WriteLine();
}
protected abstract string ReadData(string source);
protected abstract List<string> Parse(string raw);
protected virtual bool Validate(string record) => !string.IsNullOrWhiteSpace(record);
protected virtual bool ShouldNotify() => false; // hook: varsayılan kapalı
private void Save(List<string> records) =>
Console.WriteLine($" Kaydedildi: {string.Join(" | ", records)}");
private void Notify(int count) =>
Console.WriteLine($" E-posta gönderildi: {count} kayıt aktarıldı.");
}Adım 2 Alt sınıflar: sadece farklı olan adımları doldurur
public class CsvImporter : DataImporter
{
protected override string ReadData(string source)
{
Console.WriteLine($" CSV dosyası okunuyor: {source}");
return "Ali;30\n;\nAyşe;25";
}
protected override List<string> Parse(string raw) =>
raw.Split('\n').Select(line => line.Trim(';')).ToList();
}
public class JsonImporter : DataImporter
{
protected override string ReadData(string source)
{
Console.WriteLine($" API'den JSON alınıyor: {source}");
return "[\"Mehmet\",\"\",\"Zehra\",\"X\"]";
}
protected override List<string> Parse(string raw) =>
System.Text.Json.JsonSerializer.Deserialize<List<string>>(raw)!;
// Varsayılan doğrulamayı genişlet: en az 2 karakter olsun.
protected override bool Validate(string record) =>
base.Validate(record) && record.Length >= 2;
protected override bool ShouldNotify() => true; // hook'u aç
}Adım 3 Kullanım
Tüm parçaları bir araya getiren istemci (client) kodu:
DataImporter[] importers = { new CsvImporter(), new JsonImporter() };
importers[0].Import("musteriler.csv");
importers[1].Import("https://api.ornek.com/users");Konsol çıktısı
[CsvImporter] içe aktarma başladı CSV dosyası okunuyor: musteriler.csv Kaydedildi: Ali;30 | Ayşe;25 [JsonImporter] içe aktarma başladı API'den JSON alınıyor: https://api.ornek.com/users Kaydedildi: Mehmet | Zehra E-posta gönderildi: 2 kayıt aktarıldı.
Kodda dikkat edilmesi gerekenler
- İki alt sınıf da adımların sırasını değiştiremiyor; sadece içeriklerini dolduruyor.
JsonImporter,ShouldNotifyhook'unu açarak algoritmaya ek bir adım kazandırdı;CsvImporterbunu yapmadığı için bildirim gönderilmedi.- Boş kayıtlar ve JSON'daki tek harfli "X" doğrulama adımında elendi.