23 Ocak 2022 Pazar

Python ve Singleton Design Pattern (Singleton Tasarım Örüntüsü)

Merhaba,

Bu yazımda Singleton Design Pattern konusundan bahsederek python diliyle yazılmış bir örneği paylaşmak istiyorum. 

Tasarım örüntülerinde belirli bir problem için belirli bir çözüm sunulmaktadır. Bu sayede tasarım iyileştirilir ve takımın aynı dili konuşması sağlanır. Tasarım örüntüleri belirli bir işi yapmak yada çözüme ulaşmak için en iyi yöntemi sunar. Çatısı belirli yazılım örüntüleri kullanılarak oluşturulan yazılımların ilerleyen süreçlerde büyümesi, bakımının yapılması ve devamlılığının sağlanması daha kolay olacaktır.

Her tasarım örüntüsünün bir ihtiyaçtan yada bir sorunu çözme amacından ortaya çıktığını söyleyebiliriz. Singleton da bunlardan biridir. Creational Design Pattern (Yaratımsal Tasarım Örüntüsü) grubuna dahildir.



Bu tasarım örüntüsü çalışma zamanında yanlızca 1 obje yaratılmasını garanti eder. İkinci bir obje yaratılamayacaktır ve sadece bu obje çağırılıp kullanılacaktır. Kısaca tüm uygulama için bu sınıftan yanlızca bir nesne olacaktır. Bu nesneye ihtiyaç olduğunda her yerde aynı tek örnek çağırılacaktır. Böylece bir instance'a kontrollü erişim sağlayabiliriz. Singleton'da bir obje sadece ona ihtiyaç duyduğumuzda yaratılır. 

Bir sınıftan bir nesne oluşturabilirsiniz ve bu nesneyi program içinde değişik yerlerde defalarca kullanmak isteyebilirsiniz. Örneğin bir veritabanı bağlantı nesnesini bir kere oluşturup her veritabanı işlemi gerektiğinde bu nesneyi kullanabilirsiniz. Eğer burada Singleton deseni ile geliştirme yaptıysanız aynı veritabanı bağlantı sınıfından ikinci bir nesne üretilemeyecek ve her seferinde istek yapıldığında ilk yaratılan nesne örneği gelecek ve kullanılacaktır. Ayrıca bir nesneyi global bir değişkende tutmak da sıkıntılıdır, çünkü kodun herhangi bir yerinde o değişkenin üzerine yazılarak tüm kodun çökme ihtimali vardır. Singleton nesneyi global bir değişkene atamadan global bir şekilde istediğimiz yerde çağırıp kullanabilmemize izin verir. Böylece global bir kullanım sunarken başka bir kodun nesne üzerine yazması da engellenmiş olur. Kod Singleton'un statik metoduna erişebiliyorsa bu metod her çağırıldığında aynı nesne örneğinin getirilmesi sağlanacaktır.

Burada önemli noktalardan birisi de şudur. Singleton ile global değişkenler üzerinde daha sıkı bir kontrol sahibi olmuş oluruz. Singleton sınıfının kendisi dışında hiçbir kod parçası nesne örneğini değiştiremez.

Biraz da kodlar üzerinden anlamaya çalışalım. Öncelikle bir sınıf oluşturup bu sınıf ile sadece bir adet nesne üretilmesini garanti edecek basit bir kod parçacığı yazmak istiyorum.


class Singleton:
__instance = None

def __init__(self):
""" Virtually private constructor. """
if Singleton.__instance != None:
raise Exception("This class is a singleton!")
else:
Singleton.__instance = self

singObj1 = Singleton()
print(singObj1)

Yukarıdaki kodda sınıf üzerinden bir nesne yaratıyoruz. Nesne yaratıldığında sınıf içindeki __instance değişkenini kontrol ederek daha önce bu sınıftan bir nesne yaratılıp yaratılmadığını kontrol ediyoruz. Eğer daha önce nesne yaratılmadıysa nesnemiz yaratılıyor ve sınıf içindeki __instance değişkenine nesnemiz atanıyor. Ekrana bastırdığımızda da alttaki şekilde hatasız bir şekilde nesnenin yaratıldığını görüyoruz.

<__main__.Singleton object at 0x0000024535C47C10>

Şimdi alttaki şekilde ikinci bir obje yaratmaya çalışalım.


class Singleton:
__instance = None

def __init__(self):
""" Virtually private constructor. """
if Singleton.__instance != None:
raise Exception("This class is a singleton!")
else:
Singleton.__instance = self

singObj1 = Singleton()
print(singObj1)
singObj2 = Singleton()
print(singObj2)

İlk nesnenin yaratılmasına kadar herşey aynı. Artık sınıf içindeki __instance değişkenine nesnemiz atanmış durumda. İkinci kes bu sınıftan nesne üretmeye çalıştığımızda ise yine __init__ içindeki kontrol yapılacaktır. Bu sefer __instance değeri None olmayacaktır. Bu sebeple "This class is a singleton!" şeklinde bir exception fırlatacaktır. Burda dikkat ederseniz nesneyi global bir değişkende tuttuk.

Exception: This class is a singleton!

Bu örneği temel mantığı anlamanız için yaptık. Şimdi biraz daha kapsamlı bir örnek paylaşmak istiyorum. Bu örnekte singleton sınıfından bir nesne üreteceğiz ve global bir değişkene bu nesneyi atamadan her istediğimizde sınıfın metodu ile çağıracağız. Böylece nesneyi global değişkende tutarak çağırmanın getireceği risklerden de kurtulmuş olacağız. Birçok yerde paylaşılan standart bir örneği olduğu gibi paylaşarak açıklamak istiyorum.


class SingletonMeta(type):
"""
The Singleton class can be implemented in different ways in Python. Some
possible methods include: base class, decorator, metaclass. We will use the
metaclass because it is best suited for this purpose.
"""

_instances = {}

def __call__(cls, *args, **kwargs):
"""
Possible changes to the value of the `__init__` argument do not affect
the returned instance.
"""
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]


class Singleton(metaclass=SingletonMeta):
def some_business_logic(self):
"""
Finally, any singleton should define some business logic, which can be
executed on its instance.
"""
# ...


if __name__ == "__main__":
# The client code.

s1 = Singleton()
s2 = Singleton()
print(s1)
print(s2)

if id(s1) == id(s2):
print("Singleton works, both variables contain the same instance.")
else:
print("Singleton failed, variables contain different instances.")

Bu kod örneğimizde sınıfımız içinde __call__ metodunun kullanıldığını göreceksiniz. Bu metod sınıfımızdan üretilecek nesne örneğinin istenildiği bir anda bir fonksiyon gibi çağırılarak getirilebilmesini sağlar.

Yani Singleton sınıfımızdan üretilecek nesne örneğini Singleton() ile defalarca çağırabiliriz ve her seferinde aynı nesne örneği gelecektir ve ikinci bir nesne oluşturulmayacaktır. Bu tamda Singleton Design Pattern'de istediğimiz şey.

Yine burada _instances isimli bir değişkenimiz var. Bu değişken sınıf ilk nesneyi ürettiğinde oluşturulan nesne örneğini tutacak ve her çağırıldığında aynı örneği verecektir. Kontrol __call__ metodu içinde yapılacak eğer daha önce nesne üretilmediyse nesne üretilecek ve ikinci kes nesne üretilmesine izin verilmeyecektir.

Koda bakarsanız s1 ve s2 de iki kere bu sınıf nesnesi çağırılmış. Peki her seferinde aynı nesne mi gelmiş ? Evet. Aşağıdaki çıktıya baktığınızda aynı nesne örneğinin geldiğini göreceksiniz. Singleton() çağrısını yüzlerce kez de yapsak artık hep aynı nesne örneği gelecektir.

<__main__.Singleton object at 0x000002B5D5F4FF70>

<__main__.Singleton object at 0x000002B5D5F4FF70>

Singleton works, both variables contain the same instance.


Teşekkürler,

Cem Selmanoğulları

Hiç yorum yok:

Yorum Gönder