Kotlin — Singleton Pattern
Herkese merhaba, bu yazımda singleton pattern’in tanımını yapıp asynchronous işlemlerde yaşayabileceğimiz problemlerin çözümlerine bakacağız ve son olarak da dikkat etmemiz gereken noktalara değineceğiz. Yazı içerisinde Java ve Kotlin kodlarını karşılaştırarak bir inceleme yapacağız.
Yazılım tasarımı aşamasında esneklik ve verimlilik ya da etkili bir tasarım amacı taşıdığımız zaman karşımıza design pattern’ler çıkar.
Bugün bu design pattern’lerden biri olan singleton pattern üzerine konuşacağız. Peki nedir bu singleton pattern:
Singleton pattern: Yalnızca tek bir nesne oluşturulduğundan emin olarak bir nesne oluşturmakla sorumlu tek bir sınıf içerir. Bu sınıf, yalnızca tek bir nesnesine erişim sağlayan ve sınıfın nesnesini oluşturmaya gerek kalmadan doğrudan erişilebilen bir yol sağlar.
Şimdi tanım olarak öğrendiğimize göre bu öğrendiklerimiz pratikte nasıl görünüyor hemen ona bakalım. Ben bu yazı içerisinde ilk önce Java üzerinden anlatım yapacağım çünkü Kotlin tarafı bu işi çok sade bir şekilde yapıyor bu yüzden mantığını kavramak için ilk başta Java tarafını görmenin mantıklı olduğunu düşünüyorum.
public class SingleObject {
private static final SingleObject instance = new SingleObject();
private SingleObject(){}
public static SingleObject getInstance() {
return instance;
}
}
Yazmış olduğumuz class’a adım adım yaklaşalım. İlk önce constructor’ını private yaptık ardından instance’ını “private static final” olarak oluşturduk çünkü bu değişkene sadece class içerisinden erişilmesini istiyoruz(private) static bir fonksiyon içerisinde sınıfın nesnesi oluşturulmadan da kullanılmasını istiyoruz(static) ve değerinin bir kere verilip bir daha değiştirilmemesini istiyoruz(final) son olarak da “getInstance” fonksiyonu ile instance’ımıza erişim sağlatıyoruz.
Singleton bir class’ı Java’da farklı şekillerde oluşturabiliriz. Bunların bazılarına bu linkten göz atabilirsiniz.
Kotlin tarafı farklı demiştik peki bu farklılık neden kaynaklanıyor isterseniz hemen onun için de bir örnek yapalım:
object SingleObject {
}
Yazmış olduğumuz kodların hepsi Kotlin’de object keyword’ü ile karşılanıyor. Tabi ki bunu bu şekilde sözde bırakmayıp kodumuzu byteCode’dan Java’ya decompile edeceğiz:
public final class SingleObject {
@NotNull
public static final SingleObject INSTANCE = new SingleObject();
private SingleObject() {
}
}
Burada da bizim yukarıda yaptığımız işlemleri yapıyor tek bir farkla değişkene erişim “public” yapılmış. Tabi ki bunun çeşitli sebepleri vardır biz o kadar detayını sorgulamayacağız. Görmüş olduğumuz kodu yorumlayarak tek bir object keyword’ünün bu işlemlere karşılık geldiğini gözlemlemek bizim için yeterli olacaktır.
Singleton pattern’i kavramış olduk, bu pattern’i kullanırken asynchronous işlemlerde çeşitli problemle karşılaşabiliriz. Bunlardan biri farklı thread’lerin aynı değişkeni kullanması olarak söylenebilir. Bu problemin çözümü için volatile keyword’ünü kullanabiliriz.
Valotile: Değişkenler üzerinde yapılan değişiklikleri bu değişkenleri kullanan diğer thread’lere haber vermek için kullanılır.
Hemen kullanımı için de örnekler yapalım:
public class SingleObject {
private static final SingleObject instance = new SingleObject();
private static volatile int exampleNumber = 0;
private SingleObject(){}
public static int getExampleNumber() {
return exampleNumber;
}
public static void setExampleNumber(int exampleNumber) {
SingleObject.exampleNumber = exampleNumber;
}
public static SingleObject getInstance() {
return instance;
}
}
Görüldüğü üzere Java’da değişkeni oluştururken visibility modifier ve static keyword’ü sonrasında “volatile” yazarak bu keyword’ü kullanmış oluyoruz. Peki bunu Kotlin’de nasıl yapacağız. Hemen bir örnek oluşturalım:
object SingleObject {
@Volatile
var exampleNumber: Int = 0
}
Görüldüğü gibi Kotlin’de de “volatile” annotation’ı ile bu keyword’ü kullanabiliyoruz. Tabi bunu böyle bırakmayıp Java’da nasıl göründüğüne bakacağız:
public final class SingleObject {
@NotNull
public static final SingleObject INSTANCE = new SingleObject();
private static volatile int exampleNumber;
private SingleObject() {
}
public final int getExampleNumber() {
return exampleNumber;
}
public final void setExampleNumber(int var1) {
exampleNumber = var1;
}
}
Beklediğimiz gibi tam olarak bizim Java’da yazdığımız haline dönüşüyor.
Bir diğer problem ise bir fonksiyona aynı anda birden fazla thread’in erişim sağlaması olarak söylenebilir. Bu problemin çözümü için ise synchronized keyword’ü kullanılabilir.
Synchronized: Başına yazılmış olduğu fonksiyona erişen thread’lerin bu erişimi sırayla yapmasını sağlar.
Hemen bu keyword’ün kullanımı için de örnekler yapalım:
public class SingleObject {
private static final SingleObject instance = new SingleObject();
private SingleObject(){}
public static SingleObject getInstance() {
return instance;
}
public static synchronized void exampleFunc() {
}
}
Görüldüğü üzere Java’da fonksiyonu oluştururken visibility modifier ve static keyword’ü sonrasında “synchronized” yazarak bu keyword’ü kullanmış oluyoruz. Peki bunu Kotlin’de nasıl yapacağız. Hemen bir örnek oluşturalım:
object SingleObject {
@Synchronized
fun exampleFunc() {}
}
Görüldüğü gibi Kotlin’de de “synchronized” annotation’ı ile bu keyword’ü kullanabiliyoruz. Tabi bunu da burada bırakmayıp Java’da nasıl göründüğüne bakacağız:
public final class SingleObject {
@NotNull
public static final SingleObject INSTANCE = new SingleObject();
private SingleObject() {
}
public final synchronized void exampleFunc() {
}
}
Beklediğimiz gibi burası da tam olarak bizim Java’da yazdığımız haline dönüşüyor.
Bu şekilde asynchronous işlemlerde karşılaşabileceğimiz problemlerin çözümlerini de görmüş olduk.
Dikkat Edilmesi Gereken Durumlar
Singleton pattern kullanırken bazı durumlarda istemediğimiz sonuçlarla karşılaşabiliriz. Bu sorunları singleton pattern kullanmadan önce bilmek kullanımı daha etkili yapmamızı sağlar diye düşünüyorum. Dilerseniz hemen bu problemlere bakalım:
- Singleton pattern çok fazla kullanıldığı zaman memory’de kullanmadığımız nesnelerin çoğalması ile memory leak olma riski yaratır.
- Singleton pattern kullanımı bazı durumlarda backend’e sık sık istek atmamak için yapılabiliyor. Kullanılan nesnede yapılan değişikliklerin senkronizasyonuna dikkat edilmezse veritabanında bulunan veri ile bizim uygulamamızdaki veri arasında farklılıklar oluşabilir.
Bu şekilde singleton pattern nedir, kullanımı nasıl olur, asynchronous işlemlerde yaşayabileceğimiz problemleri nasıl engelleriz ve bu pattern’i kullanırken nelere dikkat etmeliyiz sorularının cevaplarını incelemiş olduk.
Vaktinizi ayırıp yazımı okuduğunuz için teşekkür ederim. Eğer içeriğim hakkında geri bildiriminiz veya sorunuz varsa benimle linkedIn üzerinden iletişime geçebilirsiniz. Sağlıklı ve mutlu kalın!
Kaynak
- https://www.tutorialspoint.com/design_pattern/singleton_pattern.htm
- https://www.digitalocean.com/community/tutorials/java-singleton-design-pattern-best-practices-examples#1
- https://www.javatpoint.com/static-keyword-in-java
- https://www.baeldung.com/java-volatile
- https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-volatile/
- https://www.baeldung.com/java-synchronized
- https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-synchronized/
- https://kotlinlang.org/docs/object-declarations.html#accessing-variables-from-anonymous-objects