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.
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.
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."
)