Behavioral — Davranışsal Desenler

Visitor

Ziyaretçi Deseni

Amaç: Bir nesne yapısındaki sınıfları değiştirmeden, bu sınıflar üzerinde çalışacak yeni işlemler eklemek.

1

Basit Anlatım

Teknik terim yok — günlük hayattan bir benzetme

Bir şehirde farklı türde binalar var: evler, fabrikalar, okullar. Şehre farklı uzmanlar gelir: vergi memuru, itfaiye denetçisi, sigorta danışmanı.

Vergi memuru eve gittiğinde emlak vergisini, fabrikaya gittiğinde ticari vergiyi hesaplar. İtfaiye denetçisi evde duman dedektörüne, fabrikada yangın merdivenine bakar. Her uzman, hangi bina türüne geldiğine göre farklı bir iş yapar.

Önemli olan şu: yeni bir uzman (ör. deprem denetçisi) şehre geldiğinde binaları yıkıp yeniden yapmanıza gerek yok. Binalar sadece kapıyı açar ve "buyurun" der. İşin nasıl yapılacağını ziyaretçi bilir.

Visitor şunu der: "Nesnelere yeni bir yetenek kazandırmak için onları değiştirme; o yeteneği taşıyan bir ziyaretçi gönder."
2

Teknik Anlamı

Bir yazılımcının bakış açısıyla

Visitor, bir nesne yapısı üzerinde yapılacak işlemi ayrı bir ziyaretçi sınıfına taşır. Her eleman bir Accept(visitor) metodu sunar ve içinde visitor.Visit(this) çağırır. Bu sayede doğru Visit overload'u elemanın gerçek tipine göre seçilir.

Double Dispatch

C# gibi dillerde hangi metodun çağrılacağı normalde tek bir nesnenin tipine göre belirlenir (single dispatch). Visitor, iki çağrı ile hem elemanın hem de ziyaretçinin tipine göre doğru kodun çalışmasını sağlar:

  1. element.Accept(visitor) → elemanın gerçek tipi belirlenir.
  2. visitor.Visit(this) → this artık somut tiptedir, doğru overload seçilir.

Katılımcılar

Ne zaman kullanılır?

Dezavantajlar

Modern C#'ta tip bazlı switch ifadeleri (pattern matching) basit durumlarda Visitor'a hafif bir alternatiftir; ancak Visitor, unutulan bir tipi derleme zamanında yakalama avantajını korur.
3

C# ile Uygulama

Adım adım, çalışan kod örneği

Senaryo: Bir sigorta şirketinin portföyünde konut, araç ve sağlık poliçeleri var. Poliçe sınıflarına dokunmadan "prim hesaplama" ve "rapor çıkarma" işlemleri ekleyeceğiz.

Adım 1 Element ve Visitor arayüzleri

public interface IPolicyVisitor
{
    void Visit(HomePolicy policy);
    void Visit(CarPolicy policy);
    void Visit(HealthPolicy policy);
}

public interface IPolicy
{
    void Accept(IPolicyVisitor visitor);
}

Adım 2 Somut elemanlar: poliçeler

Poliçe sınıfları yalnızca veri taşır ve Accept sunar. Prim hesaplama veya raporlama mantığı burada yok.

public class HomePolicy : IPolicy
{
    public decimal HouseValue { get; init; }
    public bool EarthquakeZone { get; init; }
    public void Accept(IPolicyVisitor v) => v.Visit(this);   // double dispatch
}

public class CarPolicy : IPolicy
{
    public int CarAge { get; init; }
    public int DriverAge { get; init; }
    public void Accept(IPolicyVisitor v) => v.Visit(this);
}

public class HealthPolicy : IPolicy
{
    public int Age { get; init; }
    public bool Smoker { get; init; }
    public void Accept(IPolicyVisitor v) => v.Visit(this);
}

Adım 3 Somut ziyaretçiler: yeni işlemler

Her ziyaretçi bir işlemin tüm poliçe türleri için nasıl yapılacağını tek bir sınıfta toplar.

public class PremiumCalculator : IPolicyVisitor
{
    public decimal Total { get; private set; }

    public void Visit(HomePolicy p) =>
        Add("Konut", p.HouseValue * 0.002m * (p.EarthquakeZone ? 1.5m : 1m));

    public void Visit(CarPolicy p) =>
        Add("Araç", 8_000m + p.CarAge * 400m + (p.DriverAge < 25 ? 5_000m : 0m));

    public void Visit(HealthPolicy p) =>
        Add("Sağlık", 3_000m + p.Age * 60m + (p.Smoker ? 2_500m : 0m));

    private void Add(string type, decimal premium)
    {
        Total += premium;
        Console.WriteLine($"  {type,-7} primi: {premium,10:N2} TL");
    }
}

public class RiskReport : IPolicyVisitor
{
    public void Visit(HomePolicy p) => Print("Konut", p.EarthquakeZone, "deprem bölgesi");
    public void Visit(CarPolicy p) => Print("Araç", p.DriverAge < 25, "genç sürücü");
    public void Visit(HealthPolicy p) => Print("Sağlık", p.Smoker, "sigara");

    private static void Print(string type, bool risky, string reason) =>
        Console.WriteLine($"  {type,-7}: {(risky ? $"YÜKSEK risk ({reason})" : "normal")}");
}

Adım 4 Kullanım

Tüm parçaları bir araya getiren istemci (client) kodu:

var portfolio = new List<IPolicy>
{
    new HomePolicy { HouseValue = 4_000_000m, EarthquakeZone = true },
    new CarPolicy { CarAge = 5, DriverAge = 22 },
    new HealthPolicy { Age = 40, Smoker = false },
};

Console.WriteLine("Prim hesaplama:");
var calculator = new PremiumCalculator();
portfolio.ForEach(p => p.Accept(calculator));
Console.WriteLine($"  TOPLAM        : {calculator.Total,10:N2} TL");

Console.WriteLine("\nRisk raporu:");
var report = new RiskReport();
portfolio.ForEach(p => p.Accept(report));

Konsol çıktısı

Prim hesaplama:
  Konut   primi:  12.000,00 TL
  Araç    primi:  15.000,00 TL
  Sağlık  primi:   5.400,00 TL
  TOPLAM        :  32.400,00 TL

Risk raporu:
  Konut  : YÜKSEK risk (deprem bölgesi)
  Araç   : YÜKSEK risk (genç sürücü)
  Sağlık : normal

Kodda dikkat edilmesi gerekenler

Özetle: Visitor, sabit bir sınıf yapısına, sınıfları değiştirmeden yeni işlemler eklemeyi sağlar; işlemleri ayrı ziyaretçi sınıflarında toplar.
← Ana sayfaya dön