Prototip, “Genel terminoloji ve semantikte belirli bir kategoride ele alınabilecek herhangi bir somut nesne, varlık veya olgunun geri kalanlar için örnek teşkil edebilme vasfına sahip ilk ve en ilkel türevidir. Çok öğeli bir kümede grubun kendinden sonraki diğer tüm üyelerinin karakteristik özelliklerinin karşılaştırılarak tasnif ve derecelendirme yapılabilmesini mümkün kılan en özgün öbek elemanını ima eder. ” diyor vikipedi, aslında pattern olarakta tam olarak anlamını karşılığı prototype pattern, yaratımsal tasarım kalıpları kategorisinde yer alan bir tasarım desenidir, özünde ve en kısa ifadeyle yaptığı temel iş “bir nesnenin kopyasını oluşturmak” diyebiliriz.
Pythonda nesnelerinin kopyasını oluşturmak istediğimizde shallow copy (yüzeysel kopya) ve deep copy (derin kopya) kavramları kaşımıza çıkmaktadır. Tasarım kalıbının açıklamasına ve örneğine geçmeden önce bu iki kopyalama tipine kısaca göz atalım.
Shallow Copy (Yüzeysel Kopya): orijinal objenin referanslarını ve değerlerini kopyalar, ancak iç içe geçmiş diğer objelerin ayrı kopyalarını oluşturmaz bunun yerine referanslarını tutar. Yani, shallow copy’de, orijinal obje ile kopya arasında bir ilişki vardır. Shallow copy işlemi için Python’da copy()
fonksiyonu kullanılır.
Deep Copy (Derin Kopya): hem ana objenin referanslarını hem de iç içe geçmiş tüm diğer objelerin tam kopyalarını oluşturur. Yani, deep copy işlemi için orijinal obje ile kopya arasında herhangi bir ilişki yoktur. Deep copy işlemi için Python’da deepcopy()
fonksiyonu kullanılır.
Shallow copylerde iç içe geçmiş nesnelerin kopya oluşturulan nesneye tamamen aktarılmaz orijinal nesneye bağlı kalınır buda kopya nesne üzerinde değişiklikler yaptığımızda ana nesnenin değerlerininde etkilenebileceğini anlamına gelmektedir. Bu nedenle kullanımında biraz daha dikkatli olmak gerekir, diğer taraftan büyük veri yapılarını kopyalamak için performanslı bir yöntemdir. Deep copy’de ise ana nesneden tamamen bağımsız bir kopya edindiğimizden böyle bir sorunumuz olmayacaktır, ancak büyük veri yapılarındaki performansı Shallow copy’e göre daha düşük olacaktır. Tüm bu tanımlar için kısa bir kod üzerinden örnekleyelim.
import copy
list1 = [1,2,3,4,[100,200]]
list1_shallow = copy.copy(list1)
list1_deep = copy.deepcopy(list1)
print(f"""
list1 level1 element id: {id(list1[0])} - list1 level2 element id: {id(list1[4])}
list1_shallow level1 element id: {id(list1_shallow[0])} - list1_shallow level2 element id: {id(list1_shallow[4])}
list1_deep level1 element id: {id(list1_deep[0])} - list1_deep level2 element id: {id(list1_deep[4])}
""")
list1[0] = 5
list1[4][0] = 400
print(f"original list : {list1}")
print(f"shallow copied list : {list1_shallow}")
print(f"deep copied list : {list1_deep}")
-----------------------------------------------------------------------
Output:
list1 level1 element id: 4374479088 - list1 level2 element id: 4376031040
list1_shallow level1 element id: 4374479088 - list1_shallow level2 element id: 4376031040
list1_deep level1 element id: 4374479088 - list1_deep level2 element id: 4376031232
original list : [5, 2, 3, 4, [400, 200]]
shallow copied list : [1, 2, 3, 4, [400, 200]]
deep copied list : [1, 2, 3, 4, [100, 200]]
örnekte list1 listesinden shallow copy ve deep copy ile 2 örnek oluşturuyoruz. Öncelikle shallow copy ve deep copy örnekleri için level 1 yani listenin içindeki herhangi birinci seviyede olan elamanı incelediğimizde, her birinin bellekteki adresinin aynı olduğunu görüyoruz. Ancak level 2 yani listenin içindeki 2 seviyede bulunan elamanı incelediğimizde shallow copyde list1 ile aynı adresteyken deep copyde yeni bir adrese sahip, ayrıca orjinal listede herhangi bir 1. seviye elemanı değiştirdiğimizde shallow copy ile örneğimizdeki eleman değişmezken 2. seviye bir elemanı değiştirdiğimizde kopya içindeki elemanda değişiyor bunun sebebi 2. seviye ve üstündeki elemanların shallow copy yönteminde referansının tutulması, aynı değişikliği deep copy ile oluşturulan liste için incelediğimizde ise ilk başta oluşturduğumuz list1’in aynısı olduğu görünmektedir. Tüm elemanlar kopyalandığı için özgün ve bağımsız bir liste elde etmiş olduk.
Shallow ve deep copy kavramlarını öğrendikten sonra, prototype pattern için temelimiz hazır diyebiliriz. Prototype pattern aynı zamanda clone olarakta bilinir, çözüm sunduğu temel sorun ise komplex objelerin yeniden oluşturulmasına gerek kalmadan kopyalarını oluşturup kullanabilmemizi sağlamaktadır. Bir oyun yazdığınızı düşünün farklı karakter türleri ve bu karakterler; farklı yetenek, öznitelik, silahlara sahip olsun temelde karakter olan objelerimizi farklılaştırarak oluşturmak için prototip patternden yararlanabiliriz. Kod üzerinden gidecek olursak;
import copy
class Weapon:
def __init__(self, damage, durability):
self.damage = damage
self.durability = durability
def __str__(self):
return f"Weapon: damage {self.damage}, durability {self.durability}"
class Ability:
def __init__(self, name, description):
self.name = name
self.description = description
def __str__(self):
return f"Ability: {self.name} - {self.description}"
class Character:
def __init__(self, name, health, mana, weapon, abilities):
self.name = name
self.health = health
self.mana = mana
self.weapon = weapon
self.abilities = abilities
def clone(self):
return copy.deepcopy(self)
def __str__(self):
return f"{self.name}: health {self.health}, mana {self.mana}, {self.weapon}, {', '.join([str(a) for a in self.abilities])}"
class Warrior(Character):
def __init__(self, name, health, mana, weapon, abilities):
super().__init__(name, health, mana, weapon, abilities)
class Mage(Character):
def __init__(self, name, health, mana, weapon, abilities):
super().__init__(name, health, mana, weapon, abilities)
class CharacterPrototype:
def __init__(self, warrior_prototype, mage_prototype):
self.warrior_prototype = warrior_prototype
self.mage_prototype = mage_prototype
def create_warrior(self, name):
warrior = self.warrior_prototype.clone()
warrior.name = name
return warrior
def create_mage(self, name):
mage = self.mage_prototype.clone()
mage.name = name
return mage
# silahlar ve yetenekler
sword = Weapon(50, 100)
fireball = Ability("Fireball", "A powerful fire spell")
heal = Ability("Heal", "Restores health")
buff = Ability("+5 Attack power", "Improve attack speed")
# karakterler
warrior_prototype = Warrior("Warrior", 100, 50, sword, [heal, buff])
mage_prototype = Mage("Mage", 50, 100, None, [fireball])
# karakter prototipi
character_prototype = CharacterPrototype(warrior_prototype, mage_prototype)
# yeni karakterler oluşturma
warrior1 = character_prototype.create_warrior("John")
mage1 = character_prototype.create_mage("Jane")
print(warrior1)
print(mage1)
-----------------------------------------------------------------------
Output:
John: health 100, mana 50, Weapon: damage 50, durability 100, Ability: Heal - Restores health, Ability: +5 Attack power - Improve attack speed
Jane: health 50, mana 100, None, Ability: Fireball - A powerful fire spell
Prototype pattern ile, yeni nesnelerin oluşturulması için kaynaklar (cpu, bellek) daha verimli kullanılabilir.
Nesne kopyalama, özellikle nesnelerin yaratılması çok maliyetli veya zor olduğunda, nesnelerin değiştirilmesi için başvurulabilecek iyi bir yoldur. Diğer taraftan referans verileri içeren kopyalarda veya orijinal veride programın akışındaki bir değişik olduğunda tüm kopyalar etkileneceğinden öngörülemeyecek veya gözden rahatça kaçabilecek sorunlara yol açabilir, bu nedenle kod karmaşasını önlemek ve yönetilebilirliği artırmak için ne zaman ve nasıl kullanmak gerektiğine dikkat etmek gerekmektedir.
Sources:
Be First to Comment