Makale Özeti

OOP ile yeni tanışan programcıların en çok kafa karışıklığı yaşadığı noktalardan birisi birden fazla windows formu ile çalışmak konusunda olmaktadır.

Makale

Singleton tasarım kalıbı ve Windows Formları

VB6dan VB.NETe geçen programcıların en çok kafa karışıklığı yaşadığı noktalardan birisi windows formları ile çalışmak konusunda olmaktadır. VB6 da bir formun sadece tek bir örneği olduğu için FormIsmi.Show gibi formlarla ile çalışabiliyorduk. VB.NET ile beraber Object Oriented Programming (OOP) (NYP- Nesne Yönelimli Programlama) dünyasına giren VB6 programcıları "her şey bir nesnedir" felsefesine alışmaya çalışıyor. Halen forumlarda formlarla VB6daki gibi çalışmak için neler yapılması gerektiğine dair sorular soruluyor. Bu sebeple bu makalede önce VB.NET ile OOP konusunun bir kısmından (shared nitelendirmesi) bahsedip, singleton tasarım kalıbını kullanarak formlar ile çalışacağız.

Shared Nitelendiricisi ve Singleton Tasarım Kalıbı

Artık sınıflar ile çalışmaya alışmış olmalısınız. Bir sınıfdan yeni bir nesne türettiğinizde onun constructorı olan New metodunun çalıştığını biliyorsunuz.

Örneğin aşağıdaki kod parçası bir butonun click olayına yazıldığında butona her tıklandığında Form2 sınıfından yeni bir nesne oluşturulur ve ekranda görüntülenir. New ile nesne türetmek için kod yazdığımızda kod her çağrıldığında bilgisayarın hafızasından yeni bir alan ayrılır ve orada bir nesne oluşturulur.

Dim yeniFormNesnesi As New Form2
yeniFormNesnesi.Show()

Eğer içinde bulunduğunuz duruma göre sadece bir nesne örneğine (instance) ihtiyaç duyarız. Örneğin bir uygulamada arama işlemi için diyalog penceresi sadece bir örneğe sahiptir. Menüden Arama seçeneğine kaç kere basılırsa basılsın biz sadece tek bir örneğin açık olmasını isteriz. İkinci bir form açılmasını istemeyiz.

İşte Singleton tasarım kalıbı bu amaç için kullanılır.Bu tasarım kalıbı ile bir sınıfdan türeyecek nesne sayısını teke indirebilirsiniz.

Singleton bir sınıf yapısını nasıl yazarız?

Sınıf içerisine public ve shared olarak nitelendirilmiş olan bir property yada metod ekleyerek singleton tasarım kalıbı gerçekleştirilebilir. Ayrıca sınıfın constructor metodunu da private erişim düzenleyicisi ile nitelendirilerek istenilsede bir sınıftan doğrudan (as new anahtar kelimeleri ile) nesne türetilmesi engellenebilir. Bu sınıfın bir örneğine sadece oluşturduğumuz property yada metod ile erişilebilir. Yani constructor sınıfın dışında erişime sahip değildir, sadece sınıfın içerisindeki bir metod yada property ile türetilebilir.

Peki sınıfın dışında iken bir nesne türetemiyorsak oluşturacağımız property yada metoda dışarıdan nasıl erişeceğiz?

Shared Nitelendiricisi

Oluşturulacak property yada metod shared (C#da static) erişim denetleyicisi ile nitelendirilecektir. Bir sınıf tanımlarken Shared olarak nitelendirilmiş olan sınıf öğeleri sınıfın bir örneğin olmadan SinifAdi.SharedOgeninAdi şeklinde erişilebiliyordu. Shared öğeler bir sınıftan türetilen örneklerin hiç birisine özgü değildir, oluşturulmuş bir örneklerden birisi aracılığı ile veya sınıfın adı ile erişilebilen ortak öğlerdi.

Shared nitelendiricisini kavramak için aşağıdaki örnek kodu yazıp çıktısını inceleyin.

Public Class PaylasilanSinif
Public Shared isim As String

End
Class

Yukarıdaki sınıfı projenize ekledikten sonra bir windows formu üzerinden eklediğiniz bir butonun click olayına şu kodu yazın.

Dim ornek1 As New PaylasilanSinif
ornek1.isim = "Cengiz HAN"
Dim ornek2 As New PaylasilanSinif
MsgBox(ornek2.isim)

Yukarıdaki kodlar çalıştığı zaman ekranda gözüken mesaj kutusunda "Cengiz HAN" yazacaktır. ornek1 isminde ve PaylasilanSinif türünde bir nesne oluşturuluyor. Shared olan isim özelliğine Cengiz HAN değeri atanıyor. Ardından PaylasilanSinif sınıfından ornek2 isminde yeni bir nesne oluşturuluyor. Buna herhangi bir atama yapılmadan MsgBox ile isim özelliği görüntüleniyor. Sonuç : Cengiz HAN yazan bir mesaj kutusu!!! Shared olan öğenin "paylaşılmış" bir öğe olduğunu görüyoruz.


Peki sınıfa çağrı yapan kodu şu şekilde düzenleyelim.

Dim ornek1 As New PaylasilanSinif
ornek1.isim = "Cengiz HAN"
Dim ornek2 As New PaylasilanSinif
ornek2.isim = "Rob Howard"
MsgBox(ornek1.isim)

Yukarıda az önceki koda ek oalrak ornek2 nesnesinin isim özelliği Rob Howard olarak değiştiriliyor. MsgBox çağrısında ise bu sefer ornek1.isim görüntüleniyor. Sonuç : Rob Howard yazan bir mesaj kutusu!

Anlıyoruz ki tüm örnekler ortak bir noktada bulunan isim özelliğine erişim yapıyor.

Peki kodumuzu şu şekilde değitirelim.

Dim ornek1 As New PaylasilanSinif
ornek1.isim = "Cengiz HAN"
Dim ornek2 As New PaylasilanSinif
ornek2.isim = "Rob Howard"
PaylasilanSinif.isim = "Scott Guthrie"
MsgBox(ornek1.isim)

Yukarıdaki koda daha öncekinden farklı olarak herhangi bir nesne örneği kullanmadan PaylasilanSinif.isim şeklinde sinif adı üzerinden shared olan isim özelliğine erişerek değer atanan satır eklendi. Sınıf adını kullanarak doğrudan bir öğe erişebilmek için shared olması gerekmektedir.

Bu örnekte sonuç Scott Guthrie yazan bir mesaj kutusu olacaktır. Yani herhangi bir örnek ile veya doğrudan sınıfın adı ile bir shared öğeye erişebiliriz. Ve hepsinin eriştiği öğe ortak (paylaştırılmış da diyebiliriz) bir öğedir.

Not: Shared nitelendiricisi ile bir metodu (sub veya function), özelliği (property) yada bir değişkeni shared (paylaştırılmış olarak nitelendirebilir).

Shared nitelendiricisini kullanarak bir sınıftan kaç tane nesne türetildiğini sayan bir kod parçası yazalım. (Tabiki bu sayı uygulama açık olduğu sürede oluşturulan nesne sayısını döner)

Public Class SayacliSinif
Private Shared sayac As Integer = 0
Sub New()
sayac = sayac + 1
End
Sub
Public Shared ReadOnly Property NesneSayaci() As Integer
Get
Return sayac
End Get
End Property
End Class

Yukarıdaki kod parçasında private erişime sahip fakat shared olan bir sayac değişkeni var. Bu değişken ile oluşturulan nesnelerinin sayısını öğreneceğiz. Yine shared olan readonly bir property tanımlaması var, NesneSayaci ismindeki bu property ise sayac değişkenindeki değerin dışarıdan erişime açılmasından sorumlu. Sınıfın yapılandırıcısında (constructor) ise sayac değeri 1 artırılıyor. Yapılandırıcı bir sınıftan yeni bir nesne türetildiğinde çalışan metod olduğuna göre her nesne türetildiğinde sayac değer bir aratacaktır.

Bu sınıfı test etmek için formunuz üzerine bir buton ve label kontrolü yerleştirin butonun click olayına aşağıdaki kodu yazın.

Dim yeniNesne As New SayacliSinif
Label1.Text = SayacliSinif.NesneSayaci

Yukarıdaki kod parçası her çalıştırıldığında SayacliSinif sinifindan yeni bir nesne oluşturacaktır. Ardından SayacliSinif sinifinda tanımlanan Shared property olan NesneSayacini kullanarak oluşturulmuş nesne sayısını Label1de görüntüleyecektir.

Shared nitelendiricisini öğrendik, biliyorsak hatırladık. Şimdi Singleton tasarım kalıbına dönelim. Ne demiştik ?

Sınıf içerisine public ve shared olarak nitelendirilmiş olan bir property yada metod ekleyerek singleton tasarım kalıbı gerçekleştirilebilir.

Yeni bir windows formu açın ve form içerisine şu kodu ekleyin. Formun adı SingletonForm1 olsun.

Private Shared nesneOrnegi As SingletonForm1
Public Shared ReadOnly Property GetInstance() As SingletonForm1
Get
If nesneOrnegi Is Nothing Then
nesneOrnegi = New SingletonForm1
End If
Return nesneOrnegi
End Get
End Property

Yukarıdaki kodu inceleyelim. Shared olarak tanımlanmış bir değişken var türü windows formumuzun türünde SingletonForm1.

ReadOnly ve Shared olarak tanımlanmış olan GetInstance propertysine bakarsak, kod bölümünde private ve shared olan nesneOrnegi değişkeni Nothing ise ona yeni bir nesne atıyor ve return ile geriye dönüyor. Eğer nesneOrnegi propertysine ikinci sefer erişmek istersek buradaki kod parçası bakar nesneOrnegi değişkeni Nothing mi? -Değil! Çünkü ilk çağrıda ona bir nesne atanmıştı. O zaman if içerisindeki kod ile değişkene yeni bir nesne atanmaz ve ilk çağrıda oluşturulan haliyle geriye döner. Bu durumda uygulamanın çalışma zamanında bu property ile sadece tek bir örnek üzerinde çalışmış oluruz.

Not: Şu anda formun constructorı private olarak düzenlenmediği için New SingletonForm1 şeklinde nesne oluşturulabilir. Daha öncede bahsettiğim gibi eğer bu şekilde bir nesne türetilmesinide engellemek istersek form sınıfının constructorını private olarak nitelendirmemiz gerekir.

Uygulamada tek instance olarak çalışmak istediğiniz her form sınıfını bu şekilde düzenleyin. Tabiki her form içinde SingletonForm1 yerine formunuzu ismini yazmanız gerekir.

Ardından bu formların yer aldığı projenizde StartUp Object olarak ayarlamak üzere bir Sub Main oluşturmanız gerekmektedir. Bunu bir Module ekleyip onun içine bir Sub Main yazarak halledebileceğiniz gibi elinizdeki herhangi bir form sınıfı içerisine Shared Sub Main ekleyerek de aynı işi yapabilirsiniz.

Not: Module olarak eklenen dosyalar aslında bir sınıftan farklı bir şey değildir. Tek farkı derlendiği zaman bizim özel olarak kod yazmamıza gerek bırakmadan her öğesinin Shared olarak nitelendirilmiş olmasıdır.

Elinizdeki formlardan herhangi birisine Shared Sub Main ekleyebilirsiniz. Hangisi olduğu mühim değil. Yada boş bir sınıf (class) ekleyip bunun içinede yazabilirsiniz.

O zaman uygulamanın açılış noktası olması için uygulamaya boş bir sınıf dosyası (class file) ekleyin. Ve aşağıdaki gibi düzenleyin.

Public Class SingletonAcilisSinifi

Shared Sub Main()
Application.Run(SingletonForm1.GetInstance)
End Sub


End Class

Yukarıdaki kodu yazdıktan sonra Solution Explorer penceresinden proje ismi üzerine sağ tıklayın açılan menüden Properties menü seçeneğini seçin ve Startup object olarak Sub Main seçeneğini seçin.

Bu durumda uygulama çalıştığında Sub Main çalışacaktır. Sub Main çalışıtığında ise Application.Run metodunu çağıracaktır. Buradaki Run metodu parametre olarak bir form sınıfı ister. Parametre olarak SingletonForm1.GetInstance özelliğini vererek Singleton1 sınıfının tek örneği ile uygulamayı başlatmış oluruz.

Şimdi ikinci bir form ekleyelim, SingletonForm2 ismini verelim. Kodu aşağıdaki şekilde yazalım.

Yukarıdaki kod parçası SingletonForm1 sınıfında yazdığımız kodun aynısı sadece formun ismi değiştiği için değişkenin ve propertynin türleri SingletonForm2 olarak yazılmalı.

SingletonForm2.Closing olayında (yani SingletonForm2 den türemiş bir nesnenin kapanmadan tetiklenen olayı) e.Cancel ile formun kapatılmasını iptal ediyorum. Bu şekilde yazdığım kod formun hiç bir zaman kapatma butonu ile kapatılamasını sağlar. Ancak Me.Hide() ile formumuzu gizliyorum. Bu şekiled formun tekil çalışabilirliğini sağlamış oluyorum.

Şimdi SingletonForm1 üzerine bir buton ekleyin ve butonun click olayına şu kodu yazın.

SingletonForm2.GetInstance.Show()
SingletonForm2.GetInstance.Activate()

Yukarıdaki kod parçası ile SingletonForm2 formunun tekil örneği Show metodu ile görüntüleniyor. Activate metodu ile ise zaten açık ise diğer formların üstüne gelmesi sağlanıyor.(Activate metodu çağrılmasaydı form zaten açıkken butona tıklandığında form en üstte gözükmeyecekti)

Bitirirken...

Bu şekilde formlarımız ile daha etkin çalışabiliriz. Özellikle bu şekilde çalışmak VB6dan terfi eden programcılara büyük kolaylık sağlayacaktır.

Son olarak bir müjde vermek istiyorum! VB.NET 2005 versiyonunda VB.NETe DefaultInstance isminde bir özellik ekleniyor. Singleton tasarım kalıbı ile uğraşmadan aynı VB6da olduğu gibi formismi.show, formismi.ozellik şeklinde erişim yapılabilecek. "Yaşasın!!" nidaları atana VB6cılar duyuyorum sanki? ☺☺

Önce shared nitelendiricisinden bahsettik ardından singelton tasarım kalıbı ile windows formlarının tekil örnekleri ile nasıl çalışılabileceğini inceledik.

Umarım faydalı bir makale olmuştur.

Makalede kullanılan proje dosyasını indirmek için tıklayınız.

Cengiz HAN
Microsoft ASP.NET MVP
cengiz@cengizhan.com