Tasarım Kalıplarına Genel Bakış
Tasarım kalıpları, nesneye yönelik programlamada sık karşılaşılan bazı problemlerin çözümüne yönelik olarak ortaya atılmış yöntemlerdir. Gang of Four (GOF) olarak bilinen Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides tarafından yazılmış olan Design Patterns, Elements of Reusable Object-Oriented Software 'de 23 adet temel tasarım kalıbına yer verilmiştir. GOF bu tasarım kalıplarını, Creational, Structural ve Behavioral olmak üzere üç kategoride incelemişlerdir. Daha önce Creational tasarım kalıplarından Singleton'u incelemiştik. Singleton tasarım kalıbına ilişkin makaleye ulaşmak için tıklayınız. Creational tasarım kalıplarından devam ederek bu makalede Prototype tasarım kalıbını detaylı olarak inceleyerek C# ile örnek bir kullanımını sunacağız.
Prototype Tasarım Kalıbı
Prototype tasarım kalıbının amacı, adından da anlaşılacağı üzere, bir nesneyi prototip nesne olarak kabul ederek, bu nesneden aynı özelliklere sahip yeni bir nesne kopyası oluşturmayı sağlar. Yeni nesne yaratmanın maliyetli olduğu durumlarda, yaratılmış olan bir nesnenin kopyalanması ile, ilk yaratma maliyetlerinden kurtulunmuş olup daha hızlı bir şekilde yeni nesne elde etmemize yarar. Daha sonra, yeni oluşturulan nesnenin özellikleri değiştirilebilir ve prototip olarak kullanılan ilk nesnemizin de bu değişikliklerden etkilenmemesi gerekir.
Prototype tasarım kalıbının implementasyon detaylarına geçmeden önce, kopyalama işlemini biraz açmamız gerekiyor. Burada iki türlü kopyalama işleminden bahsedebiliriz. Shallow (sığ) copy ve deep (derin) copy.
Shallow Copy
Bir nesneden shallow copy oluştururken:
Bir özellik value type ise, değeri yeni nesneye aynen kopyalanır.
Bir özellik reference type ise, yeni nesneye özelliğin referansı kopyalanır fakat refere edilen nesne kopyalanmaz. Dolayısıyla orjinal nesne ile kopyalanan nesnenin bu özellikleri aynı nesneye işaret eder. Bu iki nesneden herhangi birinin reference type özelliğinin değeri değişirse, diğerinin de değeri değişmiş olur.
.NET framework içerisinde, shallow copy için, her nesnede bulunan MemberwiseClone() metodu kullanılabilir.
Deep Copy
Bir nesneden deep copy oluştururken:
Bir özellik value type ise, değeri yeni nesneye aynen kopyalanır.
Bir özellik reference type ise, bu özelliğin işaret ettiği nesnenin de bir kopyası oluşturulur ve yeni nesnenin kopyalanacak özelliği bu yeni oluşturulan nesne kopyasını işaret eder. Dolayısıyla, bu iki nesneden herhangi birinin reference type özelliğinin değeri değişirse, diğerinin değeri etkilenmez. Çünkü iki nesnenin bu özelliklerinin işaret ettiği nesneler de farklıdır.
.NET framework içerisinde, deep copy için doğrudan kullanılabilecek bir metod yoktur. Derin kopyası oluşturulacak nesnenin serialize edilip, daha sonra kopya nesne buradan deserialize edilerek oluşturulabilir.
Prototype tasarım kalıbına ihtiyacımız olacak bir örnek düşünelim. Diyelim ki veritabanındaki verilerimizi temsil eden entity sınıflarımız var. Bu sınıflardan herhangi birinden bir nesne yarattıktan sonra, o nesneyi prototip olarak kullanarak aynı özelliklere sahip yeni bir kopya nesne oluşturmak istiyoruz. Oluşturduğumuz kopya nesnenin de daha sonra bazı özelliklerini değiştirebiliriz. Örnek kod daha açıklayıcı olacak.
Ülke verilerini temsil eden CountryEntity sınıfı ve Şehir verilerini temsil eden CityEntity sınıfımız olduğunu varsayalım.
public class CountryEntity
{
public int CountryCode { get; set; }
public string CountryName { get; set; }
public CityEntity Capital { get; set; }
public CountryEntity(int code, string name, string capitalName)
{
CountryCode = code;
CountryName = name;
Capital = new CityEntity(capitalName);
}
}
public class CityEntity
{
public string CityName { get; set; }
public CityEntity(string name)
{
CityName = name;
}
public override string ToString()
{
return CityName;
}
}
Burada birşeye dikkatinizi çekmek istiyorum. CountryEntity sınıfının Capital özelliği, CityEntity tipindedir; yani reference type bir özelliktir. CityEntity sınıfındaki tüm özellikler ise value type özelliklerdir. Şimdi yapmak istediğimiz şey, CountryEntity veya CityEntity sınıflarından nesne yarattıktan sonra, aynı özelliklere sahip kopya nesneler oluşturabilmek. Bunun için, bu sınıfların türeyeceği ve kopyalama işlemlerini içeren bir temel sınıfa ihtiyacımız var.
[Serializable()]
public abstract class EntityPrototype
{
public EntityPrototype ShallowCopy()
{
return this.MemberwiseClone() as EntityPrototype;
}
public EntityPrototype DeepCopy()
{
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Position = 0;
EntityPrototype copy = formatter.Deserialize(stream) as EntityPrototype;
stream.Close();
return copy;
}
}
EntityPrototype isimli soyut sınıfımız, hem shallow copy oluşturma , hem de deep copy oluşturma için metodları ve bu metodların implementasyonlarını içermektedir. Şimdi yapmamız gereken, CountryEntity ve CityEntity sınıflarını bu EntityPrototype sınıfından türetmek ve her ikisini de Serializable yapmak. CountryEntity ve CityEntity sınıflarımızın son hali şu şekilde olacaktır.
[Serializable]
public class CountryEntity : EntityPrototype
{
public int CountryCode { get; set; }
public string CountryName { get; set; }
public CityEntity Capital { get; set; }
public CountryEntity(int code, string name, string capitalName)
{
CountryCode = code;
CountryName = name;
Capital = new CityEntity(capitalName);
}
}
[Serializable]
public class CityEntity : EntityPrototype
{
public string CityName { get; set; }
public CityEntity(string name)
{
CityName = name;
}
public override string ToString()
{
return CityName;
}
}
Evet, şimdi shallow copy ve deep copy oluşturmak için metodlarımızı içeren temel soyut sınıfımız (EntityPrototype) ve bu sınıftan türeyen ve dolayısıyla her iki kopyalama metoduna da sahip CountryEntity ve CityEntity sınıflarımız mevcut. Bu sınıfları kullanacak bir client'ın nasıl derin ve sığ kopyaları nasıl oluşturabileceğine bir bakalım.
CountryEntity turkiye = new CountryEntity(100, "Türkiye", "Ankara");
CountryEntity shallowCopyTurkiye = turkiye.ShallowCopy() as CountryEntity;
CountryEntity deepCopyTurkiye = turkiye.DeepCopy() as CountryEntity;
CityEntity ankara = new CityEntity("Ankara");
CityEntity shallowCopyAnkara = ankara.ShallowCopy() as CityEntity;
CityEntity deepCopyAnkara = ankara.DeepCopy() as CityEntity;
Öncelikle turkiye ismiyle CountryEntity tipinde bir nesne yarattık. Daha sonra bu nesneyi ptototip olarak kullanarak shallowCopyTurkiye ve deepCopyTurkiye isimli iki nesne daha oluşturduk. Oluşturulan her iki kopya nesnenin de özellik değerleri, prototip nesnemiz olan turkiye nesnesi ile aynı olacaktır. Fakat, shallowCopyTurkiye nesnesinin Capital özelliğinde bir değişiklik yapıldığında, bu değişiklik turkiye nesnesinin Capital özelliğinin değerini de değiştirecektir. Aynı değişiklik deepCopyTurkiye nesnesinde yapıldığında ise, turkiye nesnesinin Capital özelliğinin değerinde herhangi bir değişiklik olmayacaktır. CityEntity tipindeki ankara nesnesinden oluşturulan shallowCopyAnkara ve deepCopyAnkara nesneleri arasında herhangi bir farklılık olmayacaktır. Çünkü CityEntity sınıfı herhangi bir reference type özellik içermemektedir.
Shallow copy ve deep copy işlemlerinin prototip nesneleri ve kopyalarını nasıl etkilediğini incelemek için şöyle bir test kodu yazabiliriz.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("İki prototip ülke oluşturuluyor...");
CountryEntity turkiye = new CountryEntity(100, "Türkiye", "Ankara");
CountryEntity almanya = new CountryEntity(200, "Almanya", "Berlin");
Console.Write("Prototip ülke 1 : ");
UlkeBilgileriYaz(turkiye);
Console.Write("Prototip ülke 2 : ");
UlkeBilgileriYaz(almanya);
Console.WriteLine();
Console.WriteLine("Türkiye'den shallow copy oluşturuluyor.\n----------------------------------------------");
CountryEntity sCopyTurkiye = turkiye.ShallowCopy() as CountryEntity;
Console.Write("Prototip ülke : ");
UlkeBilgileriYaz(turkiye);
Console.Write("Kopya ülke : ");
UlkeBilgileriYaz(sCopyTurkiye);
Console.WriteLine();
Console.WriteLine("Kopya ülkenin kodu değiştiriliyor. Prototip ülke bilgisi etkilenmiyor. \n----------------------------------------------");
sCopyTurkiye.CountryCode = 120;
Console.Write("Prototip ülke : ");
UlkeBilgileriYaz(turkiye);
Console.Write("Kopya ülke : ");
UlkeBilgileriYaz(sCopyTurkiye);
Console.WriteLine();
Console.WriteLine("Kopya ülkenin başkenti değiştiriliyor. Prototip ülke bilgisi de değişiyor. \n----------------------------------------------");
sCopyTurkiye.Capital.CityName = "İstanbul";
Console.Write("Prototip ülke : ");
UlkeBilgileriYaz(turkiye);
Console.Write("Kopya ülke : ");
UlkeBilgileriYaz(sCopyTurkiye);
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("Almanya'dan deep copy oluşturuluyor.\n----------------------------------------------");
CountryEntity dCopyAlmanya = almanya.DeepCopy() as CountryEntity;
Console.Write("Prototip ülke : ");
UlkeBilgileriYaz(almanya);
Console.Write("Kopya ülke : ");
UlkeBilgileriYaz(dCopyAlmanya);
Console.WriteLine();
Console.WriteLine("Kopya ülkenin kodu değiştiriliyor. Prototip ülke bilgisi etkilenmiyor. \n----------------------------------------------");
dCopyAlmanya.CountryCode = 250;
Console.Write("Prototip ülke : ");
UlkeBilgileriYaz(almanya);
Console.Write("Kopya ülke : ");
UlkeBilgileriYaz(dCopyAlmanya);
Console.WriteLine();
Console.WriteLine("Kopya ülkenin başkenti değiştiriliyor. Prototip ülke bilgisi etkilenmiyor. \n----------------------------------------------");
dCopyAlmanya.Capital.CityName = "Münih";
Console.Write("Prototip ülke : ");
UlkeBilgileriYaz(almanya);
Console.Write("Kopya ülke : ");
UlkeBilgileriYaz(dCopyAlmanya);
Console.Read();
}
static void UlkeBilgileriYaz(CountryEntity ulke)
{
Console.WriteLine(ulke.CountryCode.ToString() + " - " + ulke.CountryName + " - " + ulke.Capital.ToString());
}
}
Bu test programını çalıştırdığımızda, aşağıdaki gibi bir sonuç elde edeceğiz. Bu sonuçtan sanırım iki kopyalama arasındaki fark rahatlıkla görülebilmektedir.
İki prototip ülke oluşturuluyor...
Prototip ülke 1 : 100 - Türkiye - Ankara
Prototip ülke 2 : 200 - Almanya - Berlin
Türkiye'den shallow copy oluşturuluyor.
----------------------------------------------
Prototip ülke : 100 - Türkiye - Ankara
Kopya ülke : 100 - Türkiye - Ankara
Kopya ülkenin kodu değiştiriliyor. Prototip ülke bilgisi etkilenmiyor.
----------------------------------------------
Prototip ülke : 100 - Türkiye - Ankara
Kopya ülke : 120 - Türkiye - Ankara
Kopya ülkenin başkenti değiştiriliyor. Prototip ülke bilgisi de değişiyor.
----------------------------------------------
Prototip ülke : 100 - Türkiye - İstanbul
Kopya ülke : 120 - Türkiye - İstanbul
Almanya'dan deep copy oluşturuluyor.
----------------------------------------------
Prototip ülke : 200 - Almanya - Berlin
Kopya ülke : 200 - Almanya - Berlin
Kopya ülkenin kodu değiştiriliyor. Prototip ülke bilgisi etkilenmiyor.
----------------------------------------------
Prototip ülke : 200 - Almanya - Berlin
Kopya ülke : 250 - Almanya - Berlin
Kopya ülkenin başkenti değiştiriliyor. Prototip ülke bilgisi etkilenmiyor.
----------------------------------------------
Prototip ülke : 200 - Almanya - Berlin
Kopya ülke : 250 - Almanya - Münih
Bu da gösteriyor ki kopyalama yaparken hangi metodu kullanacağımıza dikkat etmemiz gerekir. "Deep copy en garantisi, shallow copy ile uğraşmadan heryerde deep copy kullanayım" gibi bir yaklaşım da bu tasarım deseninin başta bahsettiğimiz amacına çok uygun olmayacaktır. Çünkü deep copy oluşturma işlemi, yani bir nesneyi serialize edip daha sonra kopya nesne oluşturmak için deserialize etmek maliyetli bir iştir. İlk yaratma maliyetinden kaçınırken bu sefer de serialize-deserialize işleminin maliyeti ile karşı karşıya kalırız. Kopyalama yapacağımız nesnenin özelliklerinde reference tipi yoksa mutlaka shallow copy işlemini tercih etmeliyiz.
Son bir not daha düşelim. Kopyalama metodlarını içeren temel soyut sınıfımız EntityPrototype sınıfını, generic tip kullanarak tekrar düzenleyebiliriz. Böylece kopya oluşturma işleminde type cast yapmaya ihtiyacımız olmaz.
[Serializable()]
public abstract class EntityPrototype where T : class
{
public T ShallowCopy()
{
return this.MemberwiseClone() as T;
}
public T DeepCopy()
{
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Position = 0;
T copy = formatter.Deserialize(stream) as T;
stream.Close();
return copy;
}
}
Generic kullanılarak düzenlenmiş örnek kodları indirmek için tıklayınız. (Prototype.zip - 3,93 kb)