Merhaba. Forumda Design Pattern alt forumunun neredeyse tamamen boş olduğunu gördüm ve bu bana ufak çaplı bir şaşkınlık yarattı. İyi bir yazılımcıyı, kötü bir yazılımcıdan ayıran en büyük özelliklerden birisi projeye uygun tasarım tercihini iyi bir şekilde implemente edebilip edemediğidir. Bu tasarım tercihlerinden olabildiğince fazlasını implemente edebiliyor olmak kişinin kariyerinde çok büyük bir artı olduğu gibi kendisini geliştirme sürecinde de çok büyük bir hız kazandırır. Amerika'yı yeniden keşfetmeye gerek yok, her seferinde temiz bir kodun nasıl yazılacağını düşünüp sıfırdan sıfırdan stratejiler üretemeyiz -tabii ki belli pratikler dışında-. O yüzden bu alt başlığı birazcık doldurmaya karar verdim. Bu rehber rölatif olarak kısa olacak ama diğer tasarım tercihlerinde daha detaylı açıklamalar yapacağım için bu basit tasarımdaki bazı konseptleri anlamamız gerekiyor.
Singleton, isminden de büyük oranda anlaşılacağı üzere, "tekillik" üzerine inşa edilmiş bir tasarım düzenidir. Bir sınıfın yalnızca ve yalnızca bir adet objesinin yaratılması, herhangi bir şekilde bu objenin taşınamaması ya da kopyalanamaması, proje içerisindeki her yerde tamamen aynı obje kullanılır. Tek bir merkezden yönetilmesi gereken birçok projede -sürü olmayan dronelar, uçaklar, arabalar- bu tasarım düzenine yer verilir.
Bildiğiniz üzere C++ dilinde bir objenin global namespace'te yer alıp projenin başından sonuna kadar ömrünü korumasını istiyorsak bu objeyi "static" keyword'ünü başa koyarak tanımlarız. Bunu kısa bir örnekle gösterecek olursak:
Bu kod foo() fonksiyonu içerisinde bir x değişkeni oluşturuyor, ardından bu değişkenin değerini 1 arttırıp ekrana yazdırıyor. main fonksiyonumuzda ise foo() fonksiyonunu 10 defa çalıştırıyoruz, ve çıktımız aşağıdaki gibi oluyor.
Bunun sebebi basit bir "lifetime" özelliği, foo() fonksiyonu bitince içinde yaratılan otomatik ömürlü x nesnesi de yok ediliyor, yeniden çağırıldığında x değişkeni hayatına "0" olarak yeniden başlıyor, 1 oluyor ve yok oluyor. Şimdi eğer x değişkeni static olsaydı, "static int x{0};", x nesnesi artık otomatik ömürlü olmayacak; foo() fonksiyonu çağırıldıktan sonra dahi hayatta kalmaya devam edecek ve yeniden çağırıldığında eski değerinden işleme dahil olacaktır. Bu noktada çıkışımız:
Demek ki bir nesne her scope'tan çıktığında yok edilmesin, yeniden çağırıldığında baştan oluşturmak yerine aynısı kullanılsın istiyorsak static keywordünü kullanıyoruz. Primitif olmayan sınıfların objeleri için de bu durum geçerlidir. Şimdi kafamızda yönetici pozisyonuna koyduğumuz bir Manager sınıfı düşünelim, farklı sınıflar ise kendisini manager sınıfının barındırdığı bir listeye kaydetmek istesin.
Bu kodda acemice ve bariz bir tasarım hatası var, hem "add_int_to_list" fonksiyonu içinde bir manager değişkeni oluşturup sonra yok edip duruyor, hem de main fonksiyonunda kullanılan manager objesi ise ondan apayrı bir manager! Dolayısı ile elemanları tutması gereken vektör bomboş. Bu aslında sektörde düşündüğünüzden daha sık yapılan bir hata, her zaman bu kadar bariz olmasa da... Şimdi bu hatayı önlemeye çalışalım, sadece ve sadece bir manager olsun ve istese de istemese de herkes aynı objeyi kullansın istediğimizi varsayalım. Çok küçük iki ayar yapalım:
Bu sınıf üzerinde yaptığımız değişiklikleri özetlersek:
1 - Artık constructor private, yani dışarıdan hiçkimse yeni bir Manager objesi çağıramaz.
2 - Copy constructor ve copy operator deleted, yani hiçkimse halihazırda olan bir manager objesini kopyalayamaz. (Burada move constructorın da default oluşturulmadığına dikkat edelim!)
3 - Manager objesi sadece ve sadece getInstance() fonksiyonu üzerinden erişilebilir, bu fonksiyon da static bir Manager objesi döndürebilir. Dolayısıyla artık tüm obje oluşturduğumuz yerleri Manager::getInstance() ile değiştirmek zorundayız.
Kodun kalanına bakarsak:
Artık kodumuzun çıktısı 1-10 aralığındaki sayılar oldu, beklendiği üzere. Gördüğünüz gibi singleton tasarım düzeni sayesinde sektörde sıkça yapılan acemi bir hatayı engellemiş olduk. (Hata yeni obje yaratan junior dev'de olduğu kadar Manager sınıfının singleton olmamasına ses çıkarmayan architect'te de var
)
Bu rehberde singleton tasarım düzeninin mantığına, inceliklerine pek de girmeden değinmeye çalıştım. C++11 ve sonrası standartlar için thread-safe olan bu birkaç satırlık basit kod satırı sektörde çok önemli bir yer tutmakta ve birçok aktif projede çok yardımcı olmaktadır. Buraya kadar okuduysanız teşekkür ederim
Singleton, isminden de büyük oranda anlaşılacağı üzere, "tekillik" üzerine inşa edilmiş bir tasarım düzenidir. Bir sınıfın yalnızca ve yalnızca bir adet objesinin yaratılması, herhangi bir şekilde bu objenin taşınamaması ya da kopyalanamaması, proje içerisindeki her yerde tamamen aynı obje kullanılır. Tek bir merkezden yönetilmesi gereken birçok projede -sürü olmayan dronelar, uçaklar, arabalar- bu tasarım düzenine yer verilir.
Bildiğiniz üzere C++ dilinde bir objenin global namespace'te yer alıp projenin başından sonuna kadar ömrünü korumasını istiyorsak bu objeyi "static" keyword'ünü başa koyarak tanımlarız. Bunu kısa bir örnekle gösterecek olursak:
C++:
#include <iostream>
void foo()
{
int x{0};
x++;
std::cout << x << "\n";
}
int main()
{
for(int i = 0; i < 10; ++i)
{
foo();
}
}
Bu kod foo() fonksiyonu içerisinde bir x değişkeni oluşturuyor, ardından bu değişkenin değerini 1 arttırıp ekrana yazdırıyor. main fonksiyonumuzda ise foo() fonksiyonunu 10 defa çalıştırıyoruz, ve çıktımız aşağıdaki gibi oluyor.
Bash:
PS C:\Users\Hacknology\tht> g++ .\singleton.cpp -o singleton.exe
PS C:\Users\Hacknology\tht> .\singleton.exe
1
1
1
1
1
1
1
1
1
1
Bunun sebebi basit bir "lifetime" özelliği, foo() fonksiyonu bitince içinde yaratılan otomatik ömürlü x nesnesi de yok ediliyor, yeniden çağırıldığında x değişkeni hayatına "0" olarak yeniden başlıyor, 1 oluyor ve yok oluyor. Şimdi eğer x değişkeni static olsaydı, "static int x{0};", x nesnesi artık otomatik ömürlü olmayacak; foo() fonksiyonu çağırıldıktan sonra dahi hayatta kalmaya devam edecek ve yeniden çağırıldığında eski değerinden işleme dahil olacaktır. Bu noktada çıkışımız:
Bash:
PS C:\Users\Hacknology\tht> g++ .\singleton.cpp -o singleton.exe
PS C:\Users\Hacknology\tht> .\singleton.exe
1
2
3
4
5
6
7
8
9
10
Demek ki bir nesne her scope'tan çıktığında yok edilmesin, yeniden çağırıldığında baştan oluşturmak yerine aynısı kullanılsın istiyorsak static keywordünü kullanıyoruz. Primitif olmayan sınıfların objeleri için de bu durum geçerlidir. Şimdi kafamızda yönetici pozisyonuna koyduğumuz bir Manager sınıfı düşünelim, farklı sınıflar ise kendisini manager sınıfının barındırdığı bir listeye kaydetmek istesin.
C++:
#include <iostream>
#include <vector>
class Manager{
public:
std::vector<int> managersList;
};
void add_int_to_list(int value)
{
Manager m;
m.managersList.push_back(value);
}
int main()
{
Manager manager;
for (int i = 0; i < 10; ++i)
{
add_int_to_list(i);
}
for (const auto& i : manager.managersList)
{
std::cout << i << "\n";
}
}
Bu kodda acemice ve bariz bir tasarım hatası var, hem "add_int_to_list" fonksiyonu içinde bir manager değişkeni oluşturup sonra yok edip duruyor, hem de main fonksiyonunda kullanılan manager objesi ise ondan apayrı bir manager! Dolayısı ile elemanları tutması gereken vektör bomboş. Bu aslında sektörde düşündüğünüzden daha sık yapılan bir hata, her zaman bu kadar bariz olmasa da... Şimdi bu hatayı önlemeye çalışalım, sadece ve sadece bir manager olsun ve istese de istemese de herkes aynı objeyi kullansın istediğimizi varsayalım. Çok küçük iki ayar yapalım:
C++:
class Manager{
private:
Manager() = default;
Manager(const Manager&) = delete;
Manager& operator=(const Manager&) = delete;
public:
std::vector<int> managersList;
static Manager& getInstance()
{
static Manager instance;
return instance;
}
};
Bu sınıf üzerinde yaptığımız değişiklikleri özetlersek:
1 - Artık constructor private, yani dışarıdan hiçkimse yeni bir Manager objesi çağıramaz.
2 - Copy constructor ve copy operator deleted, yani hiçkimse halihazırda olan bir manager objesini kopyalayamaz. (Burada move constructorın da default oluşturulmadığına dikkat edelim!)
3 - Manager objesi sadece ve sadece getInstance() fonksiyonu üzerinden erişilebilir, bu fonksiyon da static bir Manager objesi döndürebilir. Dolayısıyla artık tüm obje oluşturduğumuz yerleri Manager::getInstance() ile değiştirmek zorundayız.
Kodun kalanına bakarsak:
C++:
#include <iostream>
#include <vector>
class Manager{
private:
Manager() = default;
Manager(const Manager&) = delete;
Manager& operator=(const Manager&) = delete;
public:
std::vector<int> managersList;
static Manager& getInstance()
{
static Manager instance;
return instance;
}
};
void add_int_to_list(int value)
{
Manager& m = Manager::getInstance();
m.managersList.push_back(value);
}
int main()
{
Manager& manager = Manager::getInstance();
for (int i = 0; i < 10; ++i)
{
add_int_to_list(i);
}
for (const auto& i : manager.managersList)
{
std::cout << i << "\n";
}
}
Artık kodumuzun çıktısı 1-10 aralığındaki sayılar oldu, beklendiği üzere. Gördüğünüz gibi singleton tasarım düzeni sayesinde sektörde sıkça yapılan acemi bir hatayı engellemiş olduk. (Hata yeni obje yaratan junior dev'de olduğu kadar Manager sınıfının singleton olmamasına ses çıkarmayan architect'te de var
Bu rehberde singleton tasarım düzeninin mantığına, inceliklerine pek de girmeden değinmeye çalıştım. C++11 ve sonrası standartlar için thread-safe olan bu birkaç satırlık basit kod satırı sektörde çok önemli bir yer tutmakta ve birçok aktif projede çok yardımcı olmaktadır. Buraya kadar okuduysanız teşekkür ederim



