Prototype Pattern - Prototip Deseni

Prototype pattern, kodunuzu sınıflarına bağımlı hale getirmeden mevcut nesneleri kopyalamanıza olanak tanıyan creational bir tasarım kalıbıdır.

Daha önceki Tasarım Deseni / Kalıbı / Örüntüsü (Design Pattern) Nedir? başlıklı yazımızda tanıttığımız Creational Patterns altında yer alan Prototype Pattern Nedir sorusunu ve kod örneklerini bu yazımızda oldukça detaylı paylaşıyoruz.


Diyelim ki bir nesneniz var ve onun tam bir kopyasını oluşturmak istiyorsunuz. Nasıl yapardınız? İlk olarak, aynı sınıftan yeni bir nesne oluşturmalısınız. Ardından, orijinal nesnenin tüm fieldlarını gözden geçirmeniz ve değerlerini yeni nesneye kopyalamanız gerekir.

Güzel! Ama dikkat etmeniz gereken bir nokta var. Tüm nesneler bu şekilde kopyalanamaz, çünkü nesnenin bazı fieldları private olabilir ve nesnenin dışından görünmez olabilir.

Bir nesneyi "dışarıdan" kopyalamak her zaman mümkün değildir.

Doğrudan yaklaşımla ilgili bir sorun daha var. Bir kopya oluşturmak için nesnenin sınıfını bilmeniz gerektiğinden, kodunuz o sınıfa bağımlı hale gelir. Ekstra bağımlılık sizi korkutmuyorsa bile, başka bir tuzak daha vardır. Bazen, örneğin bir fonksiyondaki bir parametre, bazı interfaceleri izleyen nesneleri kabul ettiğinde, yalnızca nesnenin izlediği interface'i bilirsiniz, ancak concrete sınıfını bilemezsiniz.

✅ Çözüm

Prototip deseni, klonlama işlemini klonlanan gerçek nesnelere devreder. Model, klonlamayı destekleyen tüm nesneler için ortak bir interface bildirir. Bu interface, kodunuzu o nesnenin sınıfına bağlamadan bir nesneyi klonlamanıza olanak tanır. Genellikle, böyle bir interface yalnızca tek bir klonlama yöntemi içerir.

Klon yönteminin uygulanması tüm sınıflarda çok benzerdir. Yöntem, geçerli sınıftan bir nesne oluşturur ve eski nesnenin tüm alan değerlerini yenisine taşır. Ayrıca private fieldları bile kopyalayabilirsiniz çünkü çoğu programlama dili, nesnelerin aynı sınıfa ait olan diğer nesnelerin private alanlarına erişmesine izin verir.

Klonlamayı destekleyen bir nesneye prototip denir. Nesneleriniz düzinelerce fielda ve yüzlerce olası konfigürasyona sahip olduğunda, bunları klonlamak alt sınıflamaya bir alternatif olarak hizmet edebilir.

Önceden oluşturulmuş prototipler, alt sınıflamaya bir alternatif olabilir.

Nasıl çalışır özetleyelim: Çeşitli konfigürasyonlarla yapılandırılmış bir dizi nesne yaratırsınız. Konfigüre ettiğiniz gibi bir nesneye ihtiyacınız olduğunda, sıfırdan yeni bir nesne oluşturmak yerine bir prototipi klonlarsınız.

🚗 Gerçek Dünya Analoji

Gerçek hayatta, bir ürünün seri üretimine başlamadan önce çeşitli testler yapmak için prototipler kullanılır. Ancak bu durumda, prototipler herhangi bir fiili üretime katılmazlar, bunun yerine pasif bir rol oynarlar.

Hücre bölünmesi

Bu süreci mitoz hücre bölünmesi sürecine benzetebiliriz. Mitoz bölünmeden sonra bir çift özdeş hücre oluşur. Orijinal hücre bir prototip görevi görür ve kopyanın oluşturulmasında aktif rol alır.

⚓Structure

1. Basit Implementasyon

Structure
  1. Prototype interface'i, klonlama yöntemlerini declare eder. Çoğu durumda, tek bir klon yöntemi vardır.
  2. Concrete Prototype sınıfı, klonlama yöntemini implement eder. Bu yöntem, orijinal nesnenin verilerini klona kopyalamaya ek olarak, bağlantılı nesnelerin klonlanması, recursive bağımlılıkların çözülmesi vb. ile ilgili klonlama işleminin bazı uç durumlarını da işleyebilir.
  3. Client, prototype interface'ini izleyen herhangi bir nesnenin bir kopyasını üretebilir.

2. Prototype Registry Implementasyon

Structure
  1. Prototype Registry, sık kullanılan prototiplere erişmenin kolay bir yolunu sağlar. Kopyalanmaya hazır bir dizi önceden oluşturulmuş nesneyi saklar. En basit prototip registry bir name → prototip hash maptir. Ancak, basit bir addan daha iyi arama kriterlerine ihtiyacınız varsa, registry'nizin çok daha sağlam bir sürümünü oluşturabilirsiniz.

# Factory Method Pseudocode

Bu Prototype deseni örneği, kodu sınıflarına bağlamadan geometrik nesnelerin tam kopyalarını oluşturmanıza olanak tanır.

Bir sınıf hiyerarşisine ait bir dizi nesneyi klonlama.

Tüm şekil sınıfları, bir klonlama yöntemi sağlayan aynı interface'i takip eder. Bir alt sınıf, elde edilen nesneye kendi field değerlerini kopyalamadan önce parent'ın klonlama yöntemini çağırabilir.

// Base prototype.
abstract class Shape is
    field X: int
    field Y: int
    field color: string

    constructor Shape() is
        // ...

    // Prototype constructor. 
    // Varolan nesneden değerlerle yeni bir nesne başlatılır.
    constructor Shape(source: Shape) is
        this()
        this.X = source.X
        this.Y = source.Y
        this.color = source.color

    // Klonlama işlemi Shape alt sınıflarından birini döndürür.
    abstract method clone():Shape


// Concrete prototype. Klonlama yöntemi yeni bir nesne oluşturur 
// ve onu constructora iletir. Constructor bitene kadar yeni bir 
// klona referansı vardır. Bu nedenle, hiç kimsenin kısmen oluşturulmuş
// bir klona erişimi yoktur. Bu, klonlama sonucunu tutarlı tutar.
class Rectangle extends Shape is
    field width: int
    field height: int

    constructor Rectangle(source: Rectangle) is
        // Parent sınıfta tanımlanan özel alanları kopyalamak 
        // için bir üst constructor çağrısı gerekir.
        super(source)
        this.width = source.width
        this.height = source.height

    method clone():Shape is
        return new Rectangle(this)


class Circle extends Shape is
    field radius: int

    constructor Circle(source: Circle) is
        super(source)
        this.radius = source.radius

    method clone():Shape is
        return new Circle(this)


// Client kodunda herhangi bir yer
class Application is
    field shapes: array of Shape

    constructor Application() is
        Circle circle = new Circle()
        circle.X = 10
        circle.Y = 10
        circle.radius = 20
        shapes.add(circle)

        Circle anotherCircle = circle.clone()
        shapes.add(anotherCircle)
        // `anotherCircle` değişkeni `circle` nesnesinin tam kopyasını içerir.

        Rectangle rectangle = new Rectangle()
        rectangle.width = 10
        rectangle.height = 20
        shapes.add(rectangle)

    method businessLogic() is
        // Prototip harikadır çünkü türü hakkında hiçbir şey bilmeden 
        // bir nesnenin bir kopyasını üretmenize izin verir.
        Array shapesCopy = new Array of Shapes.

        // Örneğin, şekiller dizisindeki öğelerin tamamını bilmiyoruz. 
        // Tek bildiğimiz, hepsinin şekil olduğu. Ancak polimorfizm sayesinde 
        // bir şekil üzerinde 'clone' yöntemini çağırdığımızda program gerçek sınıfını 
        // kontrol eder ve o sınıfta tanımlanan uygun clone yöntemini çalıştırır. 
        // Bu nedenle, bir dizi basit Shape nesnesi yerine uygun klonlar elde ederiz.
        foreach (s in shapes) do
            shapesCopy.add(s.clone())

        // `shapesCopy` dizisi, `shape` dizisinin çocuklarının kopyasını içerir

💡 Uygulanabilirlik

🐛 Kodunuzun kopyalamanız gereken concrete nesne sınıflarına bağlı olmaması gerektiğinde Prototype desenini kullanın.

⚡ Bu, kodunuz bazı interfaceler aracılığıyla 3. taraf kodundan size iletilen nesnelerle çalıştığında çok olur. Bu nesnelerin concrete sınıfları bilinmiyor ve isteseniz bile onlara güvenemezsiniz.

Prototype modeli, istemci koduna klonlamayı destekleyen tüm nesnelerle çalışmak için genel bir interface sağlar. Bu arabirim, istemci kodunu klonladığı concrete nesne sınıflarından bağımsız hale getirir.

🐛 Yalnızca ilgili nesnelerini başlatma biçiminde farklılık gösteren alt sınıfların sayısını azaltmak istediğinizde kalıbı kullanın. Birisi, belirli bir konfigürasyona sahip nesneler oluşturabilmek için bu alt sınıfları oluşturmuş olabilir.

⚡ Prototip deseni, çeşitli şekillerde yapılandırılmış bir dizi önceden oluşturulmuş nesneyi prototip olarak kullanmanıza olanak tanır.

İstemci, bazı konfigürasyonlarla eşleşen bir alt sınıfından nesne oluşturmak yerine, uygun bir prototip arayabilir ve onu klonlayabilir.

🗎 Nasıl Implemente Edeceğiz?

  1. Prototype arabirimini oluşturun ve içindeki klon yöntemini bildirin. Veya varsa, yöntemi mevcut bir sınıf hiyerarşisinin tüm sınıflarına ekleyin.
  2. Bir prototype sınıfı, o sınıfın bir nesnesini argüman olarak kabul eden alternatif constructor'ı tanımlamalıdır. Constructor, sınıfta tanımlanan tüm fieldların değerlerini iletilen nesneden yeni oluşturulan örneğe kopyalamalıdır. Bir alt sınıfı değiştiriyorsanız, üst sınıfın kendi private fieldlarını klonlanmasını işlemesine izin vermek için ana constructor'ı çağırmalısınız. Programlama diliniz methodun overloadingini desteklemiyorsa, nesne verilerini kopyalamak için özel bir method tanımlayabilirsiniz. Constructor, bunu yapmak için daha uygun bir yerdir çünkü new operatörü çağırdıktan hemen sonra ortaya çıkan nesneyi teslim eder.
  3. Klonlama yöntemi genellikle sadece bir satırdan oluşur ve buda constructor'ın prototype versiyonuyla new operatörü çalıştırmaktır. Her sınıfın klonlama yöntemini açıkça geçersiz kılması ve new operatörüyle birlikte kendi sınıf adını kullanması gerektiğini unutmayın. Aksi takdirde, klonlama yöntemi bir üst sınıfın nesnesini üretebilir.
  4. İsteğe bağlı olarak, sık kullanılan prototiplerin bir kataloğunu depolamak için merkezi bir prototype registry oluşturun. Registry'i yeni bir factory sınıfı olarak uygulayabilir veya prototipi almak için statik bir yöntemle temel prototip sınıfına koyabilirsiniz. Bu yöntem, istemci kodunun fonksiyona ilettiği arama kriterlerine göre bir prototip aramalıdır. Kriterler, basit bir dize etiketi veya karmaşık bir arama parametreleri kümesi olabilir. Uygun prototip bulunduktan sonra, registry onu klonlamalı ve kopyayı istemciye return etmelidir. Son olarak, alt sınıfların constructor'larına yapılan doğrudan çağrıları, prototype registry'nin factory yöntemine yapılan çağrılarla değiştirin.

⚖️ Prototype Avantajları ve Dezavantajları

✔️ Nesneleri, concrete sınıflarına bağlamadan klonlayabilirsiniz.

✔️ Önceden oluşturulmuş prototipleri klonlamak için tekrarlanan initialization kodundan kurtulabilirsiniz.

✔️ Karmaşık nesneleri daha rahat üretebilirsiniz.

✔️ Karmaşık nesneler için initialization ön ayarlarıyla uğraşırken kalıtım için bir alternatif elde edersiniz.

❌ Circular referansları olan karmaşık nesneleri klonlamak çok zor olabilir.

⇄ Diğer Desenlerle İlişkisi

  • Birçok tasarım, Factory Method (daha az karmaşık ve alt sınıflar aracılığıyla daha fazla özelleştirilebilir) kullanılarak başlar ve Abstract Factory, Prototype veya Builder'a (daha esnek, ancak daha karmaşık) doğru gelişir.
  • Abstract Factory sınıfları genellikle bir dizi Factory Method'a dayanır, ancak bu sınıflarda yöntemleri oluşturmak için Prototype da kullanabilirsiniz.
  • Prototype, Command pattern'in kopyalarını geçmişe kaydetmeniz gerektiğinde yardımcı olabilir.
  • Composite ve Decarator yoğun olarak kullanıldığı tasarımlar genellikle Prototype kullanımından yararlanabilir. Deseni uygulamak, karmaşık yapıları sıfırdan yeniden oluşturmak yerine klonlamanıza olanak tanır.
  • Prototype kalıtım üzerine kurulu değildir, dolayısıyla dezavantajları da yoktur. Öte yandan, Prototype, klonlanmış nesnenin karmaşık bir şekilde initialization'ınını gerektirir. Factory Method, kalıtımı temel alır ancak bir initialization adımı gerektirmez.
  • Bazen Prototype, Memento'ya daha basit bir alternatif olabilir. Bu, geçmişinde durumunu saklamak istediğiniz nesne oldukça basitse ve dış kaynaklara bağlantıları yoksa veya bağlantıların yeniden kurulması kolaysa işe yarar.
  • Abstract Factory, Builder ve Prototype Singleton olarak uygulanabilir.

<> Kod Örneği

Aşağıdaki public repo adresine giderek adresine giderek örnek Prototype kodlarını inceleyebilirsiniz.

Github repoya gitmek için Tıklayınız.