Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

C# Visitor Design Pattern(Visitor Tasarım Deseni)

Merhaba,

Bu içeriğimizde Davranışsal Tasarım Kalıplarından(Behavioral Patterns) olan Visitor Tasarım Desenini(Visitor Design Pattern) tam teferruatlı inceleyecek ve hangi senaryolarda, ne amaçla bu pattern’ı kullanabileceğimize dair teorik sorgulamada bulunurken bir yandan da pratiksel olarak birkaç örnek üzerinde istişare edeceğiz. O halde fazla vakit kaybetmeksizin buyrun konuya başlayalım…

Visitor Design Pattern Nedir?

Uygulamalarda belirli sınıflara, o sınıfların fiziksel yapılarını değiştirmeksizin yeni fonksiyonaliteler eklemek gerektiği durumlarda kullanılan bir tasarım desenidir. Evet… Bazen belirli nesneler üzerinde var olan özelliklerin dışında yeni davranışlara sahip farklı özelliklere ihtiyacımız olabilmektedir. Ee zaten bu durum OOP yaklaşımlı yazılım inşa süreçlerinin en doğal halidir. Haliyle genellikle, biz yazılımcılar bu tarz durumlarda ilgili sınıfları fiziksel olarak onarmayı ve yeni özellikleri manuel olarak eklemeyi tercih ederek bu doğal duruma karşı en temel refleksi gösteriyoruz, öyle değil mi?

Amma velakin, yeni özelliğin ekleneceği sınıfların değiştirilmesi istenmediği ya da kaynak kodu elimizde olmayan bir sınıfa yeni bir fonksiyonalitenin eklenmek istendiği durumları yaşadınız mı hiç? Yaşadıysanız eğer bu pattern’ın kıymetini zaten bildiğinize şüphem yok, yok eğer yaşamadıysanız işte bu tarz durumlar için biçilmiş kaftan olan bu pattern’ı sindirip hafızaya atmanızda fayda var!

Visitor Design Pattern, yukarıda bahsedildiği gibi uygulandığı nesnelere yeni fonksiyonaliteler ekleyebilmemizi sağlamaktadır. Böylece eklenecek olan fonksiyonalitedeki davranışı ya da algoritmayı(her ne ise), ilgili nesnelerden ayırmamıza olanak sağlayan bir stratejidir. Çünkü yazımızın devamında göreceğiniz üzere bu davranışlar/algoritmalar/fonksiyonaliteler(bundan sonra sadece davranış denecek) hedef nesnelerden ziyade farklı sınıflarda tanımlanmış olacaktır. Peki bu ne işe yarayacaktır? diye sorduğunuzda; bir davranışı, üzerinde çalıştırılacağı nesneden ayırabilmek, o davranış için yönetilebilirlik ve geliştirilebilirlik açısından daha sürdürülebilirlik kazandırırken nesne açısından da farklı davranışlara karşı oluşabilecek direnci ortadan kaldırmaktadır.

Visitor Design Pattern, dokunulmazlığı olan sınıflara yeni işlevler/fonksiyonlar/özellikler eklenmesi gerektiğinde kullanılan bir stratejidir.

Bu Pattern’ın Adı Neden ‘Visitor’ ?

Yukarıdaki satırlarda, -uygulamalarda belirli sınıflara, o sınıfların fiziksel yapılarını değiştirmeksizin yeni fonksiyonaliteler eklemek gerektiği durumlarda kullanılan bir tasarım desenidir- şeklinde Visitor pattern’ının amacını yazmıştık. Peki bu davranışa karşılık ‘Visitor’ ismi neden kullanılmaktadır? dediğinizi duyar gibiyim… Bunu şöyle izah edelim;

Bir sınıfa eklenecek yeni fonksiyonalite, Visitor pattern sayesinde o sınıfın bir memberı olmaksızın ilgili sınıfa enjekte edilebilmektedir. Misal olarak, evinize gelen bir ziyaretçiyi düşünürsek eğer esasında bu ziyaretçi evin bir üyesi olmasa da bazı işlerde size yardım ederek yeni bir fonksiyonellik kazandırabilir öyle değil mi? Hah işte sınıfa eklenecek yeni fonksiyonaliteleri de ekleme sürecinde izleyeceğimiz strateji bu mantıkla Visitor olarak nitelendirilmektedir. Yani sınıfın bir üyesi olmadan, misafir gibi gelen ve iş yapıp sonra giden farklı nesneler kastedilmektedir.

Benzer mantıkla, bu durum ihtiyaca binaen başka bir fonksiyonaliteye ihtiyaç duyulduğunda farklı bir misafirin gelmesini ifade etmektedir. Dolayısıyla bizler de yazılımdaki sınıflarımızda farklı fonksiyonalitelere olası ihtiyaçlara binaen Visitor pattern sayesinde bunları enjekte edebilir ve ihtiyaca bağlı bir şekilde eski visitor’ın kattığı fonksiyonelliği göndererek yerine yeni visitor’ın kattığı fonksiyonelliği kullanabiliriz.

Visitor Design Pattern’in Stratejisi Nasıldır?

C# Visitor Design Pattern(Visitor Tasarım Deseni)

Kaynak : https://refactoring.guru/design-patterns/visitor

Visitor Design Pattern’da ihtiyaç duyulan davranışı gerçekleştirecek ya da bir başka deyişle ihtiyaç duyulan fonksiyonelliği barındıracak visitor/ziyaretçi nesnesi gerçek/orjinal nesneye bir parametre olarak iletilir. Ardından işlevsel açıdan davranış gerçek/orjinal nesne tarafından visitor’a bırakılarak gerçekleştirilmesine müsaade edilir.

Bu mantığı uygulayabilmek için yandaki şemada görüldüğü üzere Visitor, Concrete Visitor, Element ve Concrete Element aktörleri eşliğinde Visitor pattern stratejisi sergilenebilmektedir. Şimdi gelin bu yapıların stratejik rollerini ve operasyon sürecinde ne gibi sorumluluklar taşıdıklarını inceleyelim;

  • Visitor
    Uygulamada ihtiyaca binaen yeni fonksiyonaliteleri barındıracak olan ziyaretçi sınıflarının arayüzüdür. Tüm ziyaretçiler bu arayüzden türeyerek gerçek nesnelere birden fazla davranışı dependency injection sayesinde kazandırabilmemizi sağlar.
  • Concrete Visitor
    İhtiyaç duyulan fonksiyonaliteleri barındıran somut ziyaretçi nesneleridir. Visitor arayüzünden türerler.
  • Element
    İhtiyaç duyulan yeni fonksiyonaliteleri gerçek nesnelere enjekte edebilmek için gerekli metodun imzasını barındıran arayüzdür. Bahsi geçen metot Visitor referansında bir parametreye sahiptir ve istenilen davranışı barındıran Concrete Visitor bu parametre aracılığıyla ilgili gerçek nesneye iletilir. Böylece bu arayüzü uygulayan tüm gerçek nesneler sistemdeki Visitor‘dan türeyen tüm Concrete Visitor‘ları bünyelerine alarak farklı davranışları hızlıca sergileyebilir hale gelebilirler.
  • Concrete Element
    Uygulamada yeni fonksiyonalitelerin kazandırılmak istendiği gerçek/orjinal nesnelerdir. Yani, aile üyesi olmasa da ziyaretçilerin uğrayıp iş yaptığı sınıftır. Element arayüzünü uygular.
Pratik Olarak Visitor Design Pattern

Şimdi Visitor pattern’ı daha iyi anlayabilmek ve stratejik olarak yorumlayabilmek için üç farklı örnek senaryo üzerinden pratik gerçekleştirelim.

1. Senaryo
Bir uygulamada HP ve Lexmark olmak üzere sadece yazdırma niteliklerine sahip iki printer’ın var olduğunu ve bu printer’lara Faks özelliğinin de eklenmek istendiğini düşünelim. Haliyle bu ihtiyacı Visitor Design Pattern’ı kullanarak çözümleyelim.
Çözüm

  • Adım 1
    İlk olarak Visitor arayüzünü oluşturarak başlayalım.

        //Visitor Interface
        public interface IVisitor
        {
            void Visit(HPPrinter hpPrinter);
            void Visit(LexmarkPrinter lexmarkPrinter);
        }
    

    Bu arayüz, Faks özelliğine kavuşması gereken tüm Concrete Element‘ler için ilgili davranışı barındıran bir ‘Visit’ metodu barındırmaktadır. Yukarıda görüldüğü üzere bu metotlar ilgili arayüzü implemente eden Concrete Visitor tarafından uygulanacak ve içleri gerektiği gibi Concrete Element‘e özel Faks özelliği ile doldurulacaktır.

    Ayrıca burada dikkat edilmesi gereken husus Concrete Visitor nesnelerinde hangi Concrete Element için hangi ‘Visit’ metodunun çalıştırılacağı parametredeki türden(overload’ından) belli olacaktır. Buradaki manevrayı birazdan daha net bir şekilde görmüş olacağız.

  • Adım 2
    Şimdi Concrete Element‘lere eklenecek yeni fonksiyonaliteyi fiziksel olarak barındıran Concrete Visitor sınıfını oluşturalım.

        //Concrete Visitor
        public class FaxVisitor : IVisitor
        {
            public void Visit(HPPrinter hpPrinter)
            {
                //...Process
                Console.WriteLine($"{nameof(HPPrinter)}'dan faks gönderiliyor...");
            }
    
            public void Visit(LexmarkPrinter lexmarkPrinter)
            {
                //...Process
                Console.WriteLine($"{nameof(LexmarkPrinter)}'dan faks gönderiliyor...");
            }
        }
    

    Yukarıda ilgili Concrete Visitor sınıfının ‘IVisitor’ arayüzünden türediğine dikkatinizi çekerim. Ayrıca burada tüm Concrete Element‘ler için gerekli faks operasyonları yürütülmelidir. Bizler farazi olarak yukarıdaki gibi göstermelik işlemler gerçekleştirmiş bulunmaktayız.

  • Adım 3
    Ardından Visitor arayüzünün referansı aracılığıyla, Concrete Element‘lerle yeni fonksiyonaliteyi barındıran Concrete Visitor sınıfını ilişkilendirebilmek için içinde ‘Accept’ fonksiyonunu barındıran bir arayüz tanımlayalım.

        public interface IPrinter
        {
            //Yazıcılarda zaten var olan yazdırma özelliği.
            void Print();
            //Concrete Visitor nesnesiyle bu Concrete Element nesnesini
            //ilişkilendirip, süreci Concrete Visitor nesnesine bırakmamızı
            //sağlayan metot.
            void Accept(IVisitor visit);
        }
    

    Bu arayüze göz atarsak eğer, içerisinde ‘Print’ ve ‘Accept’ fonksiyonlarının imzaları bulunmaktadır. ‘Print’, yazıcılarımızda zaten hali hazırda bulunan yazdırma özelliğini ifade ederken, ‘Accept’ ise bu nesneye o anda lazım olan farklı/yeni davranışı kazandıracak olan Concrete Visitor nesnesiyle ilişki kurulmasını ifade eder. ‘Accept’ almış olduğu Concrete Visitor nesnesi sayesinde, işlevsel sürecin ziyaretçiye devredilmesini sağlamaktadır.

  • Adım 4
    Ve son olarak Concrete Element nesnelerini tasarlayalım.

        //Concrete Element
        public class HPPrinter : IPrinter
        {
            public void Print()
            {
                //...Process
                Console.WriteLine($"{nameof(HPPrinter)} yazdırıyor...");
            }
    
            public void Accept(IVisitor visit)
                => visit.Visit(this);
        }
    

    Yukarıdaki kod bloğuna göz atarsanız eğer HP yazıcısını temsil eden Concrete Element‘in inşa edildiği görülmektedir. Burada ‘Accept’ metoduna dikkat ederseniz eğer kendisine gelen ‘IVisitor’ türünden nesnenin ‘Visit’ metodunu çağırarak this keywordü eşliğinde gerçek nesneyi Concrete Visitor nesnesine göndermekte ve bir önceki adımda bahsedildiği gibi işlevsel süreçteki sorumluluk artık Concrete Visitor nesnesine verilmektedir.

    1. Adımda vurgulanan bu manevraya Double Dispatch ismi verilmiştir.

    Bir objeye, herhangi bir metot üzerinden parametre olarak gelen farklı bir objenin, herhangi bir fonksiyonu aracılığıyla parametresinden this (C#) keywordü ile ilgili nesneyi kendi içerisine alarak kontrolün kendisine devredilmesine Double Dispatch adı verilir.

    Aynı mantıkla Lexmark yazıcısı içinde Concrete Element sınıfını tasarlayalım.

        //Concrete Element
        public class LexmarkPrinter : IPrinter
        {
            public void Print()
            {
                //...Process
                Console.WriteLine($"{nameof(LexmarkPrinter)} yazdırıyor...");
            }
    
            public void Accept(IVisitor visit)
                => visit.Visit(this);
        }
    
  • Adım 5
    Yapılan inşa neticesinde artık tasarımsal testimizi gerçekleştirebiliriz.

        class Program
        {
            static void Main(string[] args)
            {
                HPPrinter hp = new();
                LexmarkPrinter lexmark = new();
    
                hp.Print();
                lexmark.Print();
    
                IVisitor fax = new FaxVisitor();
                hp.Accept(fax);
                lexmark.Accept(fax);
            }
        }
    

    Yukarıdaki kod bloğunda 5 ile 9. satır aralığında yazıcı nesneleri tanımlanmakta ve ‘Print’ fonksiyonu sayesinde yazdırma işlemleri gerçekleştirilmektedir. Buraya kadar ilgili nesneler kendilerinde dahili olan fonksiyonalitelerini çalıştırmaktadırlar. 11 ile 13. satır aralığında ise yeni bir ‘FaxVisitor’ tanımlanmakta ve bu ziyaretçi ‘Accept’ fonksiyonu ile yazıcı sınıflarına verilerek, kontrolün ziyaretçiye geçip gerekli faks operasyonlarının yapılması sağlanmaktadır.

  • Sonuç
    Uygulamayı derleyip çalıştırdığımız da aşağıdaki ekran görüntüsündeki çıktıyı verecektir.
    C# Visitor Design Pattern(Visitor Tasarım Deseni)

İşte bu kadar 🙂 Görüldüğü üzere dokunulmazlığı olan nesnelere yeni davranışları Visitor Design Pattern ile başarıyla aktarmış bulunmaktayız. Şimdi diğer örnek senaryolara geçmeden önce bir duruma istinaden farkındalık oluşturmakta fayda görmekteyim. Dikkat ederseniz eğer Visitor Design Pattern’ı belirli nesnelerde kullanabilmek için tabi ki de uygulama ona uygun şekilde tasarlanıp, kodlanmalıdır. Öyle ha babam deyip herhangi bir ‘X’ sınıfına filanca visitor’ı verelim de yeni davranış kazansın demek mümkün değildir! Bu durum, ‘X’ nesnesi visitor alacak şekilde tasarlandığı sürece mümkündür. Yani bu tasarım başta planlanıp uygulanmalıdır! Aksi taktirde bu pattern’ın uygulanmadığı bir nesnede yine bu pattern’ın kullanılması imkansızdır!

Bir sınıfın herhangi bir fonksiyonunun işlevini daha da genişletmek için Decorator, sınıfın yeni davranış/işlev/fonksiyon kazanması için Visitor kullanılır!

Şimdi diğer senaryolarımızı canlandırmaya geçebiliriz.

2. Senaryo
‘Dot(Nokta)’, ‘Circle(Daire)’ ve ‘Rectangle(Dikdörtgen)’ şeklinde üç geometrik şeklin olduğu bir uygulamada tüm bu şekilleri JSON yahut XML formatında bizlere verecek bir çalışmanın Visitor Design Pattern ile nasıl yapıldığını inceleyelim.
Çözüm
Artık mevcut sınıflarımızın var olan herhangi bir fonksiyonunun işlevini genişletmekten ziyade bu sınıflara yeni bir işlev kazandırmamız gerektiğinin farkına vararak Visitor Design Pattern ile çözümü düşünüp, gerçekleştirmeye başlayabiliriz.

  • Adım 1
    Önceki senaryoda olduğu gibi yine ilk etapta Visitor arayüzünü tasarlayarak başlayalım.

        //Visitor Interface
        public interface IShapeVisitor
        {
            void Visit(Dot dot);
            void Visit(Circle circle);
            void Visit(Rectangle rectangle);
        }
    
  • Adım 2
    Ardından Concrete Visitor sınıflarını oluşturalım. Evet, sınıf(lar)ını diyoruz çünkü bu senaryoda JSON ve XML formatında veri verecek farklı davranışlara ihtiyacımız vardır. Dolayısıyla her iki davranışı sağlayacak iki farklı Concrete Visitor‘a ihtiyacımız olacaktır.

        //Concrete Visitor
        public class JSONExportVisitor : IShapeVisitor
        {
            public void Visit(Dot dot)
                => Console.WriteLine(JsonSerializer.Serialize(new
                {
                    Location = new Point(dot.X, dot.Y),
                    dot.Shape
                }));
    
            public void Visit(Circle circle)
                => Console.WriteLine(JsonSerializer.Serialize(new
                {
                    Location = new Point(circle.X, circle.Y),
                    circle.Shape,
                    circle.Radius
                }));
    
            public void Visit(Rectangle rectangle)
                => Console.WriteLine(JsonSerializer.Serialize(new
                {
                    Location = new Point(rectangle.X, rectangle.Y),
                    rectangle.Shape,
                    Size = new Size(rectangle.Width, rectangle.Height)
                }));
        }
    
        //Concrete Visitor
        public class XMLExportVisitor : IShapeVisitor
        {
            void Report(object data)
            {
                XmlSerializer xmlSerializer = new(data.GetType());
                StringWriter stringWriter = new();
                xmlSerializer.Serialize(stringWriter, data);
                Console.WriteLine(stringWriter.ToString());
            }
            public void Visit(Dot dot)
                => Report(dot);
    
            public void Visit(Circle circle)
                => Report(circle);
    
            public void Visit(Rectangle rectangle)
                => Report(rectangle);
        }
    
  • Adım 3
    Sıra Concrete Element‘lerle yeni fonksiyonaliteyi barındıran Concrete Visitor sınıflarını ilişkilendirebilmek için ‘Accept’ fonksiyonunu barındıran arayüzü tanımlamaya gelmiştir.

        public interface IShape
        {
            void Move(int x, int y);
            void Draw();
            void Accept(IShapeVisitor shapeVisitor);
        }
    
  • Adım 4
    Son olarak Concrete Element nesnelerimizi tasarlayalım.

        //Concrete Element
        public class Dot : IShape
        {
            public int X { get; set; }
            public int Y { get; set; }
            public string Shape { get; } = ".";
    
            public void Accept(IShapeVisitor shapeVisitor)
                => shapeVisitor.Visit(this);
    
            public void Draw()
                => Console.WriteLine(Shape);
    
            public void Move(int x, int y)
                => Console.WriteLine($"Nokta X: {(X = x)} | Y: {(Y = y)} koordinatlarına taşınmıştır.");
        }
    
        //Concrete Element
        public class Circle : IShape
        {
            public int Radius { get; set; } = 10;
            public int X { get; set; }
            public int Y { get; set; }
            public string Shape { get; } = "⬤";
    
            public void Accept(IShapeVisitor visitor)
                => visitor.Visit(this);
    
            public void Draw()
                => Console.WriteLine(Shape);
    
            public void Move(int x, int y)
                => Console.WriteLine($"Daire X: {(X = x)} | Y: {(Y = y)} koordinatlarına taşınmıştır.");
        }
    
        //Concrete Element
        public class Rectangle : IShape
        {
            public int Width { get; set; } = 20;
            public int Height { get; set; } = 5;
            public int X { get; set; }
            public int Y { get; set; }
            public string Shape { get; } = "▭";
    
            public void Accept(IShapeVisitor visitor)
                => visitor.Visit(this);
    
            public void Draw()
                => Console.WriteLine(Shape);
    
            public void Move(int x, int y)
                => Console.WriteLine($"Dikdörtgen X: {(X = x)} | Y: {(Y = y)} koordinatlarına taşınmıştır.");
        }
    
  • Adım 5
    Nihai olarak inşa edilen bu tasarımı aşağıdaki gibi test edelim.

        class Program
        {
            static void Main(string[] args)
            {
                Dot dot = new();
                Circle circle = new();
                Rectangle rectangle = new();
    
                dot.Move(3, 5);
                circle.Move(13, 15);
                rectangle.Move(23, 25);
    
                Console.WriteLine("-----------");
    
                IShapeVisitor shapeVisitor = new JSONExportVisitor();
                dot.Accept(shapeVisitor);
                circle.Accept(shapeVisitor);
                rectangle.Accept(shapeVisitor);
    
                Console.WriteLine("-----------");
    
                shapeVisitor = new XMLExportVisitor();
                dot.Accept(shapeVisitor);
                circle.Accept(shapeVisitor);
                rectangle.Accept(shapeVisitor);
            }
        }
    

    C# Visitor Design Pattern(Visitor Tasarım Deseni)

Evet, bu örneğimizde işte bu kadar. Şimdi konuya dair son teorik temaslarda bulunmadan önce sıcağı sıcağına sonuncu senaryomuz üzerinden pratik gerçekleştirelim.

3. Senaryo
‘TextBox’, ‘ComboBox’ ve ‘RadioButton’ gibi toolların hem stillerini tasarlamamızı sağlayacak hem de member bilgilerini verecek olan çalışmayı Visitor Design Pattern ile gerçekleştirelim.
Çözüm
Ne demiştik! Mevcudiyetteki sınıflara yeni bir işlev kazandırılması gerektiği zaman aklımıza Visitor Design Pattern gelmeli… Haliyle bu senaryoda da mevcut olan toollara stil çalışması ve member bilgisi davranışları ekleneceğinden dolayı çözümü ilgili pattern ile düşünmeye başlıyoruz. Tabi bu senaryomuzu diğerlerine nazaran daha yalın bir şekilde uygulayacak ve direkt çözüm odaklı bir yaklaşım sergileyeceğiz.

  • Adım 1
    Visitor arayüzünü oluşturalım.

        //Visitor Interface
        public interface IToolVisitor
        {
            void Visit(TextBox textBox);
            void Visit(ComboBox comboBox);
            void Visit(RadioButton radioButton);
        }
    
  • Adım 2
    Concrete Visitor sınıflarını oluşturalım.

        //Concrete Visitor
        public class MemberDetailVisitor : IToolVisitor
        {
            public void Visit(TextBox textBox)
                => Console.WriteLine($"{nameof(TextBox)} tool detayı...");
    
            public void Visit(ComboBox comboBox)
                => Console.WriteLine($"{nameof(ComboBox)} tool detayı...");
    
            public void Visit(RadioButton radioButton)
                => Console.WriteLine($"{nameof(RadioButton)} tool detayı...");
        }
    
        //Concrete Visitor
        public class StyleVisitor : IToolVisitor
        {
            public void Visit(TextBox textBox)
            {
                //...Process
                Console.WriteLine($"{nameof(TextBox)}'a stil uygulanmıştır.");
            }
    
            public void Visit(ComboBox comboBox)
            {
                //...Process
                Console.WriteLine($"{nameof(ComboBox)}'a stil uygulanmıştır.");
            }
    
            public void Visit(RadioButton radioButton)
            {
                //...Process
                Console.WriteLine($"{nameof(RadioButton)}'a stil uygulanmıştır.");
            }
        }
    
  • Adım 3
    ‘Accept’ fonksiyonunu uygulatacak olan arayüzü oluşturalım.

        public interface ITool
        {
            void Accept(IToolVisitor visitor);
            void Use();
        }
    
  • Adım 4
    Concrete Element sınıflarını oluşturalım.

        //Concrete Element
        public class TextBox : ITool
        {
            public string Name { get; set; }
            public Color FontColor { get; set; }
            public Color BackGroundColor { get; set; }
    
            public void Accept(IToolVisitor visitor)
                => visitor.Visit(this);
    
            public void Use()
                => Console.WriteLine($"{nameof(TextBox)} kullanılmıştır.");
        }
    
        //Concrete Element
        public class ComboBox : ITool
        {
            public bool ScrollBar { get; set; }
    
            public void Accept(IToolVisitor visitor)
                => visitor.Visit(this);
    
            public void Use()
                => Console.WriteLine($"{nameof(ComboBox)} kullanılmıştır.");
        }
    

    ban

        //Concrete Element
        public class RadioButton : ITool
        {
            public bool Selected { get; set; } = false;
            public string Text { get; set; }
    
            public void Accept(IToolVisitor visitor)
                => visitor.Visit(this);
    
            public void Use()
                => Console.WriteLine($"{nameof(RadioButton)} kullanılmıştır.");
        }
    
  • Adım 5
    Test Edelim.

        class Program
        {
            static void Main(string[] args)
            {
                TextBox textBox = new();
                ComboBox comboBox = new();
                RadioButton radioButton = new();
    
                textBox.Use();
                comboBox.Use();
                radioButton.Use();
    
                Console.WriteLine("--------------");
    
                IToolVisitor toolVisitor = new MemberDetailVisitor();
                textBox.Accept(toolVisitor);
                comboBox.Accept(toolVisitor);
                radioButton.Accept(toolVisitor);
    
                Console.WriteLine("--------------");
    
                toolVisitor = new StyleVisitor();
                textBox.Accept(toolVisitor);
                comboBox.Accept(toolVisitor);
                radioButton.Accept(toolVisitor);
            }
        }
    

    C# Visitor Design Pattern(Visitor Tasarım Deseni)

Visitor Design Pattern, yeni fonksiyonların üretim maliyetlerini ana nesnelerden arındırıp oluşturabilmek için kullanılır!

Lehte ve Aleyhte Durumlar
Açık Kapalı Prensibi(Open Closed Principle – OCP)
Concrete Element sınıflarını değiştirmeksizin farklı davranışları kazandırabilmektedir.
Sürece yeni bir Concrete Element eklendiğinde Visitor arayüzü ile birlikte Concrete Visitor sınıfları güncellenmelidir!
Tek Sorumluluk Prensibi(Single Responsibility Principle – SRP)
Concrete Visitor sınıfları, tek bir davranışa odaklanmıştır.
Kritik

Visitor Design Pattern ile kod if(x is Type1) ... else if(x is Type2) ... gibi kontrollerden arındırılmış olur…

Evet, Visitor pattern bir grup sınıf üyesine dahil edilecek yeni fonksiyonaliteyi sağlarken, overloading sayesinde if(x is Type1) ... else if(x is Type2) ... gibi kontrollerden kodu arındırır ve böylece daha dinamik ve geliştirilebilir bir yapı ortaya koyar.

Bunu genellikle birden çok türden nesne barındıran koleksiyonlarda toplu işlemler yaparken kullanabiliriz. Örneğin; içerisinde ‘Product’ ve ‘Employee’ nesnelerini karışık olarak barındıran bir koleksiyondaki ‘Product’ ve ‘Employee’ bazlı toplam gideri Visitor Design Pattern ile hesaplayan aşağıdaki yapıyı inceleyiniz.

    class Program
    {
        static void Main(string[] args)
        {
            List<IOperation> operations = new()
            {
                new Product { ProductId = 1, Price = 10 },
                new Employee { EmployeeId = 1, Salary = 20 },
                new Product { ProductId = 2, Price = 5 },
                new Employee { EmployeeId = 2, Salary = 7 },
                new Product { ProductId = 3, Price = 21 },
                new Employee { EmployeeId = 3, Salary = 13 },
                new Product { ProductId = 4, Price = 33 },
                new Employee { EmployeeId = 4, Salary = 12 },
                new Product { ProductId = 5, Price = 15 },
                new Employee { EmployeeId = 6, Salary = 14 },
            };

            CalculateCostVisitor calculateCostVisitor = new();
            operations.ForEach(o => o.Accept(calculateCostVisitor));

            Console.WriteLine($"Calculated Employee Cost : {calculateCostVisitor.CalculatedEmployeeCost}");
            Console.WriteLine($"Calculated Product Cost : {calculateCostVisitor.CalculatedProductCost}");
        }
    }

    //Visitor Interface
    public interface IVisitor
    {
        void Visit(Product product);
        void Visit(Employee employee);
    }

    //Concrete Visitor
    public class CalculateCostVisitor : IVisitor
    {
        public int CalculatedProductCost { get; set; }
        public int CalculatedEmployeeCost { get; set; }

        public void Visit(Product product)
            => CalculatedProductCost += product.Price;

        public void Visit(Employee employee)
            => CalculatedEmployeeCost += employee.Salary;
    }

    public interface IOperation
    {
        void Accept(IVisitor visitor);
    }

    //Concrete Element
    public class Product : IOperation
    {
        public int ProductId { get; set; }
        public int Price { get; set; }

        public void Accept(IVisitor visitor)
            => visitor.Visit(this);
    }

    //Concrete Element
    public class Employee : IOperation
    {
        public int EmployeeId { get; set; }
        public int Salary { get; set; }

        public void Accept(IVisitor visitor)
            => visitor.Visit(this);
    }
Diğer Kalıplarla İlişkisi

Visitor Design Pattern’ın, sınıflara yeni fonksiyonalite ekleme amacından ötürü genellikle akıllara Decorator Design Pattern gelmektedir. Lakin Decorator, bir sınıfa yeni bir metot eklemekten ziyade mevcut metodun işlevselliğini zenginleştirmek için kullanılmaktadır.

Visitor Pattern’ın Extension Function İle Benzerliği

C# programlama dili, kaynak kodu elimizde olmayan/erişilemeyen yahut değişmemesi gereken bir sınıfa yeni bir fonksiyonalite/operasyon ekleyebilmemiz için Extension Function özelliğini sunmaktadır. Extension Function, C# açısından bir syntactic sugar‘dır. Yani C# syntax’ın da var olan bir özelliktir.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir