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

Twin Design Pattern(İkiz Tasarım Deseni)

Merhaba,

Bu içeriğimizde çoklu kalıtımı desteklemeyen C#, Java vs. gibi dillerde çoklu kalıtımı simüle etmemizi sağlayan Twin Design Pattern(İkiz Tasarım Deseni)’ı inceliyor olacağız.

Twin Pattern, yukarıdaki giriş cümlesinde de bahsedildiği üzere çoklu kalıtımı simüle edebilmek için standart bir çözüm sunan bir modeldir. Bu pattern sayesinde çoklu kalıtım ile ilgili isim çakışmaları(name clashes) sorunlarından kaçınılabilmekte ve ortaya koyduğu mantık ile çoklu kalıtımın getirdiği olası birçok sorun önlenebilmektedir.

Çoklu kalıtım(multiple inheritance), bir class’ın birden fazla base class’ı olması anlamına gelmektedir. Kimi programcılar açısından olmazsa olmaz bir özellik olarak iddia ediliyor olsa da isim çatışmalarına(name clashes), karmaşıklığa(complexity) ve tüm bunlar neticesinde verimsizliğe(inefficiency) yol açtığı için genel kanı olarak sorunlara kaynak teşkil etmekte ve bundan dolayı olumsuz bir nitelik olarak değerlendirilmektedir. Haliyle bu yüzden C# ve Java gibi dillerde çoklu kalıtım dilin doğasında engellenmiştir. Böylece zoraki olarak çoklu kalıtımdan arındırılmış kod yapısı sayesinde yazılım mimarileri doğal olarak daha temiz ve basit hale gelmiş bulunmaktadır.

Şimdi gelin, çoklu kalıtımın davranışsal yapısıyla Twin pattern’ı mukayese edebilmek için diyagram şemaları üzerinden modelleyelim.

Çoklu Kalıtımın Tipik Yapısı Twin Pattern’ın Yapısı
Twin Design Pattern(İkiz Tasarım Deseni) Twin Design Pattern(İkiz Tasarım Deseni)
Yukarıdaki görseli incelerseniz eğer base class rolünde olan ‘class1’ ve ‘class2’ sınıflarından kalıtım alan ‘class3’ sınıfı mevcuttur. ‘class1’ sınıfı ‘m1’, ‘class2’ sınıfı ‘m2’ ve ‘class3’ sınıfı ‘m3’ memberlarına sahiptir ve kalıtım neticesinde ‘class3’ sınıfında tüm memberların aktarıldığı gözükmektedir. Yukarıdaki görselde ise ‘child1’ ve ‘child2’ olmak üzere iki alt sınıf görmekteyiz. Bu sınıflar kendi aralarında referanslaşmaktadırlar ve üst sınıflardaki metotları override edebilmektedirler. Böylece ‘class1’ ve ‘class2’yi miras alan bu sınıfların kendi aralarında haberleşmesi bizlere bir çoklu kalıtım senaryosu sağlamaktadır.
Şöyle ki, ‘class1’ ile ‘class2’de ki memberların kalıtımsal açıdan tekbir sınıfta toplanmasını istiyorsak bu programatik olarak(C# ve Java için) mümkün değil demiştik. Amma velakin, ilgili sınıflardan türeyen child sınıflar, o sınıfların memberlarını kalıtımsal olarak miras almaktadırlar ve ardından kendi aralarında haberleşerek bu mirasları birbirleriyle paylaşarak çoklu kalıtım(mış) gibi bir davranış sergilenmesine kapı aralamaktadırlar.

Bu durumu aşağıdaki örnek kod çalışmasıyla daha da net aydınlatabiliriz kanaatindeyim;

Öncelikle kalıtımsal olarak memberlarına erişim göstermek istediğimiz base class’ları yaratalım:

class class1
{
    public void m1()
        => Console.WriteLine($"{nameof(class1)}.{nameof(class1.m1)} fonksiyonu.");
}

class class2
{
    public void m2()
        => Console.WriteLine($"{nameof(class2)}.{nameof(class2.m2)} fonksiyonu.");
}

Ardından bu class’ların child’larını:

class child1 : class1
{
    public child2 child2 { get; set; }
    public void m3()
        => Console.WriteLine($"{nameof(child1)}.{nameof(child1.m3)} fonksiyonu.");
}
class child2 : class2
{
    public child1 child1 { get; set; }
    public void m4()
     => Console.WriteLine($"{nameof(child2)}.{nameof(child2.m4)} fonksiyonu.");
}

Yukarıdaki child sınıfların yapısına dikkat ederseniz her ikisi de birbirlerini referans etmektedirler. Şimdi bu yapıyı kullanırsak:

child1 c = new child1();
c.child2 = new child2();
c.m1();
c.child2.m2();
c.m3();
c.child2.m4();

Görüldüğü üzere ‘child1’ sınıfı üzerinden hem ‘class1’ hem de ‘class2’de ki memberlara erişim göstermiş bulunmaktayız. Yani sanki ‘child1’ her iki sınıftan da kalıtım almış gibi bir davranış sergilemektedir. Tabi bunun yanında ‘child2’nin de memberlarından(m4) istifade edebilmektedir. Bu arada yukarıdaki kodu derleyip, çalıştırdığımızda aşağıdaki gibi bir netice vereceğini unutmadan sunalım.Twin Design Pattern(İkiz Tasarım Deseni)Tabi bu neticenin birebir aynısını ‘child2’ nesnesini de aşağıdaki gibi kullanarak alabilirdik:

child2 c = new child2();
c.child1 = new();
c.child1.m1();
c.m2();
c.child1.m3();
c.m4();

Yukarıdaki yaptığımız küçük örnek üzerinden dikkat ederseniz eğer Twin nesneler birbirlerine erişebildiklerinden dolayı client bu nesnelerden sadece birine doğrudan diğerine ise o nesne üzerinden dolaylı bir yoldan erişim sağlamaktadır.

Tabi bu pattern’ın tek sınırlılığı Twin nesnelerin, birbirlerine doğrudan olan erişimleri sebebiyle sıkı bir bağımlılıklarının olmasıdır. Haa bu bağımlılık neticesinde birbirlerinin memberlarını da gönül rahatlığıyla kullanabildiklerinden dolayı bu durum göz ardı edilebilmektedir.

Twin pattern, kalıtımdan daha az verimli ve performanslı olabilir. Ama ihtiyaca binaen kullanıldığı noktalarda bu durum gönül rahatlığıyla göz ardı edilebilir.

Gerçek Senaryo

Şimdi gelin içeriğimizi gerçek bir senaryoyla taçlandırarak, noktalayalım.

SENARYO
Kesme, boyut büyültme/küçültme, birleştirme vs. gibi türlü döküman işlemleri gerçekleştiren bir uygulama geliştirdiğimizi düşünelim. Bu uygulama içerisinde ‘PDF’ ve ‘Excel’ operasyonları gerçekleştirmek için hali hazırda gelen ‘PDFOperation’ ve ‘ExcelOperation’ sınıflarının olduğunu varsayalım. Bizler ihtiyaç olarak her iki sınıftan da özellik ve fonksiyonellikleri alarak, bünyesinde toplayan ve bütünsel olarak işlem yapmamızı sağlayacak olan bir sınıfa odaklanacağız ve bu ihtiyacı Twin pattern ile sağlıyor olacağız.
ÇÖZÜM
  • Adım 1
    İlk olarak memberları alınacak olan ‘PDFOperation’ ve ‘ExcelOperation’ sınıflarını sembolik olarak temsil ederek başlayalım:
    ‘PDFOperation’;

        public class PDFOperation : IOperation
        {
            public object Convert(object file)
            {
                Console.WriteLine($"{nameof(PDFOperation)}.{nameof(PDFOperation.Convert)} fonksiyonu.");
                //... Convert operasyonu ...
                return file;
            }
            public object Merge(object[] files)
            {
                Console.WriteLine($"{nameof(PDFOperation)}.{nameof(PDFOperation.Merge)} fonksiyonu.");
                //... Merge operasyonu ...
                return files;
            }
            public object SizeEnlargement(object file)
            {
                Console.WriteLine($"{nameof(PDFOperation)}.{nameof(PDFOperation.SizeEnlargement)} fonksiyonu.");
                //... SizeEnlargement operasyonu ...
                return file;
            }
            public object SizeReduction(object file)
            {
                Console.WriteLine($"{nameof(PDFOperation)}.{nameof(PDFOperation.SizeReduction)} fonksiyonu.");
                //... SizeReduction operasyonu ...
                return file;
            }
            public object Splitting(object file)
            {
                Console.WriteLine($"{nameof(PDFOperation)}.{nameof(PDFOperation.Splitting)} fonksiyonu.");
                //... Splitting operasyonu ...
                return file;
            }
        }
    

    ‘ExcelOperation’;

        public class ExcelOperation : IOperation
        {
            public object Convert(object file)
            {
                Console.WriteLine($"{nameof(ExcelOperation)}.{nameof(ExcelOperation.Convert)} fonksiyonu.");
                //... Convert operasyonu ...
                return file;
            }
            public object Merge(object[] files)
            {
                Console.WriteLine($"{nameof(ExcelOperation)}.{nameof(ExcelOperation.Merge)} fonksiyonu.");
                //... Merge operasyonu ...
                return files;
            }
            public object SizeEnlargement(object file)
            {
                Console.WriteLine($"{nameof(ExcelOperation)}.{nameof(ExcelOperation.SizeEnlargement)} fonksiyonu.");
                //... SizeEnlargement operasyonu ...
                return file;
            }
            public object SizeReduction(object file)
            {
                Console.WriteLine($"{nameof(ExcelOperation)}.{nameof(ExcelOperation.SizeReduction)} fonksiyonu.");
                //... SizeReduction operasyonu ...
                return file;
            }
            public object Splitting(object file)
            {
                Console.WriteLine($"{nameof(ExcelOperation)}.{nameof(ExcelOperation.Splitting)} fonksiyonu.");
                //... Splitting operasyonu ...
                return file;
            }
        }
    

    Tabi normal şartlarda bu sınıflar kullanılan kütüphane/dll üzerinden geleceklerdir. Zaten kaynak kodu elimizde olmayan ya da kaynak kodunu bozmak istemediğimiz sınıflarda çoklu kalıtım senaryolarını uygulayabilmek için Twin pattern kullanılmaktadır. Aksi taktirde ilgili sınıfların kaynakları bizdeyse ve erişilebilir bir vaziyetteyse yahut gönül rahatlığıyla değişiklik yapabiliyorsak direkt olarak ilgili sınıflarda birbirlerini referans verdirebilir yahut birine diğerinden kalıtım aldırarak meseleyi çözebilirdik.

  • Adım 2
    Ardından bu sınıflardan kalıtım alan ve biryandan da birbirlerini refere eden child sınıfları oluşturalım:
    ‘ChildPDFOperation’;

        public class ChildPDFOperation : PDFOperation
        {
            public ChildExcelOperation Excel { get; set; }
            public bool SendMail(object file)
            {
                //... SendMail operasyonu ....
                return true;
            }
        }
    

    ‘ChildExcelOperation’;

        public class ChildExcelOperation : ExcelOperation
        {
            public ChildPDFOperation PDF { get; set; }
        }
    

    Tabi ki de bu sınıflar kendi içlerinde özel memberlar da barındırabilmektedirler.

  • Adım 3
    Ve son olarak child sınıflardan herhangi birini kullanarak hem PDF hem de Excel operasyonlarını tek nesne üzerinden gönül rahatlığıyla yürütelim.

    ChildPDFOperation pdf = new();
    pdf.Excel = new();
    pdf.Convert(null);
    pdf.Excel.Merge(null);
    

    Twin Design Pattern(İkiz Tasarım Deseni)Görüldüğü üzere böylece çoklu kalıtım senaryosu başarıyla uygulanmıştır.

Nihai olarak, basit ama ihtiyaç doğrultusunda oldukça etkili olan bir pattern’ı örnekler eşliğinde incelemiş olduk.
Sabırla eşlik ettiğiniz için teşekkür ederim.

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

Not : Kaynak kodları aşağıdaki Github adresinden edinebilirsiniz.
https://github.com/gncyyldz/TwinDesignPattern

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

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