Bu yazı Design Patterns/Tasarım Desenleri nedir? başlıklı yazı dizisinin bir parçasıdır.

Bu içerik ağırlıklı olarak refactoring.guru sitesindeki içeriğin tercümesi ve derlenmesinden oluşturulmuştur.

Tüm tasarım desenleri ya da diğer adıyla tasarım kalıplarına yönelik ayrıntılı içeriklere yazının sonundaki bağlantılardan ulaşabilirsiniz.

Prototype deseninin amacı

Prototype (Clone) bir objeyi, kodunuz onun sınıflarına bağımlı hale gelmeden kopyalamayı sağlayan bir tasarım desenidir.

Sorun

Bir nesneniz olduğunu ve onun birebir kopyasını oluşturmak istediğinizi düşünün. Bunu nasıl yaparsınız? Önce aynı sınıf (class) da bir obje oluşturmalısınız. Ardından orijinal nesnenin tüm alanları üzerinden geçip değerleri yeni objenize atamalısınız. Yalnız burada bir sorun var. Tüm nesneleri bu şekilde kopyalamak mümkün değil, çünkü bazı alanları özel (private) tanımlanmış ve objenin dışından görülemiyor olabilir.

Bir nesneyi “dışarıdan” kopyalamak her zaman mümkün olmayabilir.

Bu doğrudan yöntemle ilgili bir sorun daha var. Kopyasını oluşturmak için nesnenin (object) sınıfını (class) bilmek zorunda olduğunuz için kodunuz o sınıfa bağımlı olur, daha da kötüsü bazen tüm sınıfı (concrete class) değil sadece o sınıfın dikkate aldığı arayüzü (interface) biliyor olursunuz.

Çözüm

Prototype tasarım deseni klonlama sürecini klonlanan nesneye delege eder ve bunu klonlama destekleyen tüm nesneler için ortak bir arayüz belirleyerek yapar. Bu arayüz kodunuzu bu nesnenin sınıfına bağımlı hale getirmeden nesneyi klonlamanızı sağlar. Böyle bir arayüz genellikle sadece bir clone metodu içerir.

clone metodunun uygulanması bir çok sınıf için benzerdir. Bu metod mevcut sınıfta bir nesne oluşturur ve tüm alan değerlerini eski objeden yenisine taşır. Bir çok programlama dili aynı sınıftan nesnelerin diğer nesnenin özel (private) sınıflarına ulaşmasına izin verdiği için özel (private) alanları dahi kopyalayabilirsiniz.

Klonlamaya izin veren nesnelere protoype denir. Nesneleriniz onlarca alan ve yüzlerce farklı yapılandırmaya sahipse klonlama (cloning) alt sınıflara (subclass) iyi bir alternatiftir.

Önceden hazırlanmış prototipler alt sınıflara bir alternatiftir.

Farzedin ki farklı özellik ve yapılandırmalarda bir çok nesne oluşturdunuz. Daha önce oluştuduğunuz bir nesneye benzer bir nesneye ihtiyacınız olduğunda sıfırdan oluşturmak yerine eski nesnenin prototipini klonlayabilirsiniz.

Uygulanabilirlik

Kodunuzun diğer sınıflardan bağımsız olması gereken durumlarda prototip tasarım desenini kullanabilirsiniz.

Bu genellikle kodunuza üçüncü parti kodlardan bir arayüz yoluyla sizin kodunuz nesneler gönderildiğinde başınıza gelir. Bu nesnelerin sınıflarının ne yaptığı belli değidlir ve isteseniz de onlara bağımlı olamazsınız.

Prototip deseni istemci koda klonlama destekleyen tüm nesnelerle çalışmak için bir arayüz sağlar. Bu arayüz istemci kodu kopyaladığı nesnenin concrete sınıflarından ayrı tutar.

Aralarındaki tek fark objeleri oluşturma biçimleri olan alt sınıfların sayısını azaltmak için bu deseni kullanabilirsiniz. Aksi halde sadece yapılandırmaları (configuration) farklı olan benzer özelliklteki nesneleri oluşturmak için alt sınıflar oluşturmanız gerekir.

Prototip deseni önceden oluşturulmuş ve yapılandırılmış nesneleri prototip olarak kullanmanıza olanak sağlar. Belirli yapılandırmaya sahipo bir alt sınıfı sıfırdan oluşturmak yerine, uygun bir prototipi klonlayabilirsiniz.

Diğer tasarım desenleri/kalıpları ile ilişkisi

  • Bir çok tasarımı Factory Method kullanarak başlar (komplike değildir ve alt sınıflarla yapılandırılabilir) ve bunların bazıları Prototip’e evrilir.
  • Prototipler bir davranışsal tasarım deseni olan komutların (commands) kopyalarını kaydetmek istediğinizde yardımcı olabilir.
  • Composite ve Decorator desenlerini yoğun kullanan tasarımlarda prototip deseni faydalı olabilir. Prototip deseni kompleks yapıları tekrar oluşturmak yerine basitçe klonlamanızı sağlar.
  • Prototip deseni inheritance tabanlı olmadığı için inheritance dezavantajlarından etkilenmez ama kopyalanan nesne için komplike bir başlatma adımı gerektirir. Factory Method inheritance tabanlıdır ama bir başlatma adımı gerektirmez.
  • Bazen prototipler Memento’ya basit bir alternatif olabilir. Bu durumunu (state) saklamak istediğiniz nesne basit ve dış kaynaklarla bağlantıyısı olmayan veya bu kaynaklara yeniden bağlanması kolay bir nesne ise geçerlidir.
  • Prototipler Abstrract Factory ve Builder’lar gibi Singleton olarak uyarlanabilir.

Prototype Kod Örnekleri

Örnek PHP Kodu

<?php

namespace RefactoringGuru\Prototype\Conceptual;

/**
 * The example class that has cloning ability. We'll see how the values of field
 * with different types will be cloned.
 */
class Prototype
{
    public $primitive;
    public $component;
    public $circularReference;

    /**
     * PHP has built-in cloning support. You can `clone` an object without
     * defining any special methods as long as it has fields of primitive types.
     * Fields containing objects retain their references in a cloned object.
     * Therefore, in some cases, you might want to clone those referenced
     * objects as well. You can do this in a special `__clone()` method.
     */
    public function __clone()
    {
        $this->component = clone $this->component;

        // Cloning an object that has a nested object with backreference
        // requires special treatment. After the cloning is completed, the
        // nested object should point to the cloned object, instead of the
        // original object.
        $this->circularReference = clone $this->circularReference;
        $this->circularReference->prototype = $this;
    }
}

class ComponentWithBackReference
{
    public $prototype;

    /**
     * Note that the constructor won't be executed during cloning. If you have
     * complex logic inside the constructor, you may need to execute it in the
     * `__clone` method as well.
     */
    public function __construct(Prototype $prototype)
    {
        $this->prototype = $prototype;
    }
}

/**
 * The client code.
 */
function clientCode()
{
    $p1 = new Prototype();
    $p1->primitive = 245;
    $p1->component = new \DateTime();
    $p1->circularReference = new ComponentWithBackReference($p1);

    $p2 = clone $p1;
    if ($p1->primitive === $p2->primitive) {
        echo "Primitive field values have been carried over to a clone. Yay!\n";
    } else {
        echo "Primitive field values have not been copied. Booo!\n";
    }
    if ($p1->component === $p2->component) {
        echo "Simple component has not been cloned. Booo!\n";
    } else {
        echo "Simple component has been cloned. Yay!\n";
    }

    if ($p1->circularReference === $p2->circularReference) {
        echo "Component with back reference has not been cloned. Booo!\n";
    } else {
        echo "Component with back reference has been cloned. Yay!\n";
    }

    if ($p1->circularReference->prototype === $p2->circularReference->prototype) {
        echo "Component with back reference is linked to original object. Booo!\n";
    } else {
        echo "Component with back reference is linked to the clone. Yay!\n";
    }
}

clientCode();

Örnek Python Kodu

import copy


class SelfReferencingEntity:
    def __init__(self):
        self.parent = None

    def set_parent(self, parent):
        self.parent = parent


class SomeComponent:
    """
    Python provides its own interface of Prototype via `copy.copy` and
    `copy.deepcopy` functions. And any class that wants to implement custom
    implementations have to override `__copy__` and `__deepcopy__` member
    functions.
    """

    def __init__(self, some_int, some_list_of_objects, some_circular_ref):
        self.some_int = some_int
        self.some_list_of_objects = some_list_of_objects
        self.some_circular_ref = some_circular_ref

    def __copy__(self):
        """
        Create a shallow copy. This method will be called whenever someone calls
        `copy.copy` with this object and the returned value is returned as the
        new shallow copy.
        """

        # First, let's create copies of the nested objects.
        some_list_of_objects = copy.copy(self.some_list_of_objects)
        some_circular_ref = copy.copy(self.some_circular_ref)

        # Then, let's clone the object itself, using the prepared clones of the
        # nested objects.
        new = self.__class__(
            self.some_int, some_list_of_objects, some_circular_ref
        )
        new.__dict__.update(self.__dict__)

        return new

    def __deepcopy__(self, memo={}):
        """
        Create a deep copy. This method will be called whenever someone calls
        `copy.deepcopy` with this object and the returned value is returned as
        the new deep copy.

        What is the use of the argument `memo`? Memo is the dictionary that is
        used by the `deepcopy` library to prevent infinite recursive copies in
        instances of circular references. Pass it to all the `deepcopy` calls
        you make in the `__deepcopy__` implementation to prevent infinite
        recursions.
        """

        # First, let's create copies of the nested objects.
        some_list_of_objects = copy.deepcopy(self.some_list_of_objects, memo)
        some_circular_ref = copy.deepcopy(self.some_circular_ref, memo)

        # Then, let's clone the object itself, using the prepared clones of the
        # nested objects.
        new = self.__class__(
            self.some_int, some_list_of_objects, some_circular_ref
        )
        new.__dict__ = copy.deepcopy(self.__dict__, memo)

        return new


if __name__ == "__main__":

    list_of_objects = [1, {1, 2, 3}, [1, 2, 3]]
    circular_ref = SelfReferencingEntity()
    component = SomeComponent(23, list_of_objects, circular_ref)
    circular_ref.set_parent(component)

    shallow_copied_component = copy.copy(component)

    # Let's change the list in shallow_copied_component and see if it changes in
    # component.
    shallow_copied_component.some_list_of_objects.append("another object")
    if component.some_list_of_objects[-1] == "another object":
        print(
            "Adding elements to `shallow_copied_component`'s "
            "some_list_of_objects adds it to `component`'s "
            "some_list_of_objects."
        )
    else:
        print(
            "Adding elements to `shallow_copied_component`'s "
            "some_list_of_objects doesn't add it to `component`'s "
            "some_list_of_objects."
        )

    # Let's change the set in the list of objects.
    component.some_list_of_objects[1].add(4)
    if 4 in shallow_copied_component.some_list_of_objects[1]:
        print(
            "Changing objects in the `component`'s some_list_of_objects "
            "changes that object in `shallow_copied_component`'s "
            "some_list_of_objects."
        )
    else:
        print(
            "Changing objects in the `component`'s some_list_of_objects "
            "doesn't change that object in `shallow_copied_component`'s "
            "some_list_of_objects."
        )

    deep_copied_component = copy.deepcopy(component)

    # Let's change the list in deep_copied_component and see if it changes in
    # component.
    deep_copied_component.some_list_of_objects.append("one more object")
    if component.some_list_of_objects[-1] == "one more object":
        print(
            "Adding elements to `deep_copied_component`'s "
            "some_list_of_objects adds it to `component`'s "
            "some_list_of_objects."
        )
    else:
        print(
            "Adding elements to `deep_copied_component`'s "
            "some_list_of_objects doesn't add it to `component`'s "
            "some_list_of_objects."
        )

    # Let's change the set in the list of objects.
    component.some_list_of_objects[1].add(10)
    if 10 in deep_copied_component.some_list_of_objects[1]:
        print(
            "Changing objects in the `component`'s some_list_of_objects "
            "changes that object in `deep_copied_component`'s "
            "some_list_of_objects."
        )
    else:
        print(
            "Changing objects in the `component`'s some_list_of_objects "
            "doesn't change that object in `deep_copied_component`'s "
            "some_list_of_objects."
        )

    print(
        f"id(deep_copied_component.some_circular_ref.parent): "
        f"{id(deep_copied_component.some_circular_ref.parent)}"
    )
    print(
        f"id(deep_copied_component.some_circular_ref.parent.some_circular_ref.parent): "
        f"{id(deep_copied_component.some_circular_ref.parent.some_circular_ref.parent)}"
    )
    print(
        "^^ This shows that deepcopied objects contain same reference, they "
        "are not cloned repeatedly."
    )

Diğer Tasarım Kalıpları/Design Patterns

Yaratımsal Kalıplar (Creational Patterns)

Yapısal Kalıplar (Structural Patterns)

Davranışsal Kalıplar (Behavioral Patterns)