Design Pattern Serisi 2: Object Pool

Merhaba, bu seferki yazımda bir başka design pattern anlatacağım. Projelerimizde sınıfların oluşturulması bazen pahalıya patlayabiliyor. Bellekten gereksiz yer ayırma (allocation) durumunda performans sorunları ortaya çıkabiliyor. Bu tür performans sorunlarını çözmek için sınıfların yeniden oluşturulmasını engellemek bir çözüm olabilir. Bunun için Object Pool Design Pattern bulunmakta. Ayrıca, .NET Framework SQL Connection üzerinde bu Design Pattern kullanılmaktadır.

Serinin bir önceki yazısında anlattığım Singleton Pattern’i burada kullanmamız gerekecek çünkü Object Pool tek elden yönetilmesi ve kullanılması gerekmektedir. Ayrıca, kullanılmasını veya üretilmesini istediğimiz sınıfın abstract olarak tanımlayıp sadece Object Pool assembly’si tarafından üretilmesini sağlayacağız. Ayrıca, kaynak kodları makalenin sonunda bulunmaktadır.

Öncelikle yapımızın UML Class Diagram halini göstermek istiyorum. Buradaki amacımız Client sınıfına doğrudan erişimi kapatıp, bu sınıfa ait tek elden türetilmiş bir Client sınıfı elde etmek istiyoruz. Olayın amacını daha detaylı anlatmak gerekirse, AcquireObject() metotu ile oluşturulmasını beklediğimiz objenin oluşturulmasını yada bize hazırda olan objeyi vermesini bekliyoruz. Burada bir istisna bulunmakta, eğer havuz boş ise ve oluşturulması gereken obje sayısı sınıra ulaşılmış ise havuzdan null pointer dönmektedir. Ancak bu kısım için Exception atılması da sağlanabilir. ReleaseObject() metotu ile kullanmış olduğumuz objeyi geri iade ediyoruz. Burada dikkat edilmesi gereken en önemli nokta alınan objenin kullanıldıktan sonra havuza geri verilmesi, eğer geri verilmezse havuzda eksik objeler bulunacak. Ve sistem eksik bir şekilde çalışmasına devam edecek. Eğer havuzun limiti size yetmiyorsa IncreaseSize() metotu ile havuzu büyütebiliriz. Temel olarak ulaşmak istediğimiz yapı bu, artık bu diyagramı koda dökebiliriz.

Önceki makalede yaptığım gibi burada da C# dilinde devam edip .NET Core üzerinden gerçekleyeceğiz.

Client sınıfımızın yapısından bahsederek başlamak istiyorum. Tasarladığınız sistemde ClientPool, Client ve RequestClient sınıfları ayrı bi DLL içerisinde bulunduğunu varsayalım. Client sınıfı abstract olarak yazıldığı için kendi başına varlığını sürdüremeyecektir, bu nedenle RequestClient sınıfı Client sınıfından türetilmiştir. Buradaki dikkat edilmesi gereken nokta RequestClient sınıfının internal anahtar sözcüğü ile tanımlanmış olması. Bu nedenle o DLL dışında hiç bir yerde üretilmeyeceğini ve erişilemeyeceğini garanti ediyoruz.

Sıra geldi ana sınıfımıza, öncelikle sınıfımız thread-safe Singleton Pattern’i sağlamaktadır. Bu sayede proje üzerinde sadece tek bir ClientPool sınıfı kullanabilir halde olacağız. Sınıfımızda maksimum üretilebilecek Client sayısını ayarlamamız için _currentSize değişkeni bulunmaktadır. Görüldüğü üzere havuzun başlangıç boyutunu 5 olarak belirledim ancak tabii ki bu değişebilir. Ayrıca, sınıf tamamen thread-safe olarak kodlanmıştır. Bunu sağlamak için ConcurrentBag<T> adlı .NET sınıfı ile birlikte AcquireObject ve IncreaseSize metotlarını lock kullanarak thread-safe yapmış bulunuyoruz.

AcquireObject metotunu anlatarak devam edeceğim, öncelikle _bag listemizden obje almaya çalışıyoruz _bag.TryTake(out Client item) kodu ile aldığımız objenin durumunu kontrol ediyoruz eğer obje doğru ise objemizi dönderiyoruz eğer obje yok ise havuzun durumuna bakarak yeni bir obje oluşturuyoruz yada null pointer dönderiyoruz.

ReleaseObject metotu ile de almış olduğumuz objeleri sisteme geri iade ederek yeniden kullanıma sunuyoruz. Geri bırakılmadığı taktirde, kaynakların doğru kullanımı gerçekleşemeyecektir.

IncreaseSize metotu havuzun boyutunu büyütmek için kullanılmaktadır. Tabii ki sisteminizin gerekliliklerine göre bu metot değiştirilebilir yada varyasyonları eklenebilir.

Örnek kullanım görelim.

Yukarıdaki kodun çıktısı aşağıdaki gibidir.

Havuzun boyutu 5
Client sınıfı ediniyoruz.
Connecting to something with RequestClient...
Client'ı geri bırakıyoruz
Uygun olan tüm Client nesneleri listeye eklendi.
Daha fazla Client sınıfı bulunmamaktadır.
Havuzun boyutunu arttırıyoruz
Yeni bir Client sınıfı ediniyoruz.
Connecting to something with RequestClient...
Edindiğimiz sınıfı geri veriyoruz.
Listedeki tüm Client sınıflarını geri bırakıyoruz.

Object Pool Design Pattern’ı gerçekledik. Bu gerçekleme sırasında hazırlamış olduğunuz Client ve RequestClient sınıflarını sadece bulunduğu DLL üzerinde üretilmesini sağladık. Bu yaklaşım ile RequestClient sınıfımızı kısıtlayarak özelleştirmelere kapattık. Ayrıca, ClientPool sınıfının tüm metotlarını thread-safe yaparak daha tutarlı bir yapı elde ettik.

Değinmek istediğim bir başka nokta ise, bu gerçeklemede havuza boyut verdik ama sizin durumlarınızda bu boyut yerine zaman kısıtlaması kullanabilirsiniz. Örneğin obje 1 dakika kullanılmazsa belleğe geri verilmesi gibi. Veyahut, bu sınırlamaları komple kaldırıp gerektiğinde yeni obje oluşturmasını ya da var olanı yeniden kullanım için vermesini sağlayabilirsiniz. (Kaynaklardaki Microsoft’un örneği bu şekildedir.)

Kaynak kodlarına buradan ulaşabilirsiniz. Bir sonraki yazıda görüşmek üzere.

ITU. CE. LCWaikiki www.alimozdemir.com

ITU. CE. LCWaikiki www.alimozdemir.com