Kotlin — Design Patterns — Creatinal Patterns (Kurucu Desenler)
Creatinal Patterns , yazılım nesnelerinin nasıl yaratılacağı hakkında genel olarak öneriler sunarak kullandığı esnek yapı sayesinde daha önceden belirlenen durumlara bağlı olarak gerekli nesneleri yaratır.
GoF’da(Gang of Four) 5 tane Creatinal Patterns’tan söz edilmiştir.
- Singleton Design Pattern
- Builder Design Pattern
- Factory Method
- Abstract Factory Pattern
- Prototype Design Pattern
Şimdi bu tasarım kalıplarını tek tek ele alalım ve Kotlin dilinde nasıl yazılıyor görelim.
Singleton Design Pattern ( Tekil Tasarım Deseni )
Uygulamanın yaşam süresince nesnenin sadece bir defa oluşturulmasını amaçlayan bir tasarım desenidir. Oluşturulan bir sınıftan sadece bir nesne yaratılacak şekilde bir kısıtlama yapabilme olanağı sağlar ve nesneye ilk kez ihtiyaç duyulana kadar yaratılmayabilir.
object Singleton {
init {
println("Nesne oluşturuluyor: $this")
}
var count = 0
fun addCount() = println(++count)
}
fun main(args: Array<String>) {
Singleton.addCount()
Singleton.addCount()
// Çıktı
// 1
// 2}
Singleton pattern yukarıdaki kod bloğundaki gibi tanımlanabilir.Uygulama boyunca Singleton nesnesinden bellekte sadece bir tane vardır. Yerine göre kullanıldığında etkin bir biçimde kullanılabilir.
Builder Design Pattern ( Yapıcı Tasarım Deseni )
Tek ara yüz kullanarak karmaşık bir nesne grubundan gerektiğince parça yaratılmasını sağlar. Nesne grubu kullanıldıkça istenilen şekilde yapılanır ve bu sayede kullanılmayan parçaların gereksiz yere yaratılarak kaynak harcama durumu ortadan kaldırılmış olur.
Kotlin bu design pattern data class yardımıyla çok kolay bir şekilde yapılabilir.
data class User(val firstName: String,
val lastName: String,
val age: Int = 0,
val phone: String? = null,
val address: String? = null)
Burada nesne oluşturulurken sadece firstName
ve lastName
istenir ancak istenirse age
,phone
ve address
değişkenleride girilebilir. Bu işlemlede yukarda bahsettiğimiz olay gerçekleşir.Nesne grubu kullanıldıkça istenilen şekilde yapılanır ve bu sayede kullanılmayan parçaların gereksiz yere yaratılarak kaynak harcama durumu ortadan kaldırılmış olur.
Bu sınıftan aşağıdaki örneklerdeki gibi nesne yaratılabilir.
val user = User("John", "Doe", phone = "555-1212")
val user = User("John", "Doe")
val user = User("John", "Doe", adress= "Silicon Valley")
Görüldüğü gibi istediğimiz parçaları kullanarak birbirinden farklı yapıda nesneler oluşturabiliyoruz.
Factory Method ( Fabrika Metodu )
Nesnenin nasıl yaratılacağını kalıtım yoluyla alt sınıflara bırakıp nesne yaratımı için tek ara yüz kullanarak, ara yüzle nesne yaratım işlevlerini temelde birbirinden ayırmaya yarayan yaratımsal tasarım kalıbıdır.
Factory Method deseninin ana amacı, “genişletilebilirlik” tir. Birbirinden yapısal olarak farklı ancak aynı zamanda birçok karakteristik özelliği ortak olan nesnelerin yönetimi, oluşturma kıstaslarının belirlenmesi ve yaratılması için Factory Metodu kullanılır.
Örneğin hangi ülkenin hangi para birimini kullandığını söyleyen bir uygulama yapalım,
interface Currency {
val code: String
}
enum class Country {
UnitedStates, Turkey, UK, Greece
}class Euro(override val code: String = "EUR") : Currencyclass Lira(override val code: String = "TL") : Currency
Yukarıda gördüğünüz enum class
kavramı programlama dillerinde kodun daha iyi okunabilmesi için oluşturulmuş sabitlerdir. Euro
,Lira
classları Currency
interface’sini implemente ederek code
değişkenine kendi para birimlerini atamıştır.
Dikkat edilirse yukarda bahsettiğimiz gibi Euro
,Lira
classlarının karakteristik özellikleri aynıdır ve daha sonra isteğe göre Dollar
, Sterlin
gibi aynı yapıda classlar oluşturularak Factory Method’un “genişletilebilirlik” ilkesi uygulanabilir.
Şimdi bize lazım olan şey Euro
,Lira
classlarını oluşturacak bir tane Factory class’ı oluşturmaktır. Bu class’ıda aşağıdaki şekilde oluşturabiliriz.
class CurrencyFactory {
fun currencyForCountry(country: Country): Currency? {
return when (country) {
Country.Spain, Country.Greece -> Euro()
Country.Turkey -> Lira()
else -> null
}
}
}
CurrencyFactory
class’ı bizim Factory class’ımızdır yani Euro
,Lira
classlarını oluşturan ve geriye döndüren class’tır.
fun main(args: Array<String>) {
val noCurrencyCode = "No Currency Code Available"
val currencyFactory = CurrencyFactory()
val greeceCode = currencyFactory.currencyForCountry(Country.Greece)?.code ?: noCurrencyCode
println("Greece currency: $greeceCode")
val trCode = currencyFactory.currencyForCountry(Country.Turkey)?.code ?: noCurrencyCode
println("TR currency: $trCode")
val ukCode = currencyFactory.currencyForCountry(Country.UK)?.code ?: noCurrencyCode
println("UK currency: $ukCode")
}
Yukarıda görüldüğü üzere currencyForCountry
fonksiyonuna yollanan parametrelere göre Euro
,Lira
class’larını oluşturuyoruz.
Abstract Factory Pattern ( Soyut Fabrika Tasarım Deseni )
Abstract Factorytasarım deseni, birbirinden farklı ama ilişki nesneleri, soyut bir çatı altından üretilmesini modelleyen bir desendir.
Bu tasarım deseninin Factory Method’tan farkı Factory class’ı bir kere tanımlandıktan sonra hep aynı tür nesne üretmesidir. Aşağıdaki örnekte görüldüğü üzere PlantFactory
class’ına OrangePlant
referans verilmiştir.Bu durumda PlantFactory
class’ının ApplePlant
üretmesi beklenemez ancak Factory Method’ta CurrencyFactory
class’ını bir kere tanımladıktan sonra Euro
,Lira
class’larınu oluşturabilmiştik.
interface Plant
class OrangePlant : Plant
class ApplePlant : Plant
abstract class PlantFactory {
abstract fun makePlant(): Plant
companion object {
inline fun <reified T : Plant> createFactory(): PlantFactory = when (T::class) {
OrangePlant::class -> OrangeFactory()
ApplePlant::class -> AppleFactory()
else -> throw IllegalArgumentException()
}
}
}
class AppleFactory : PlantFactory() {
override fun makePlant(): Plant = ApplePlant()
}
class OrangeFactory : PlantFactory() {
override fun makePlant(): Plant = OrangePlant()
}
fun main(args: Array<String>) {
val plantFactory = PlantFactory.createFactory<OrangePlant>()
val plant = plantFactory.makePlant()
println("Created plant: $plant")
}
Prototype Design Pattern ( Örnek Tasarım Deseni )
Kendi üzerinden yaratılacak nesneler için prototip görevi üstlenen bir yapı sunmaktadır. Diğer bir deyişle, sınıflardan nesne yaratırken yeni nesnelerin baştan yaratılmayıp, mevcutlarını örnek kabul ederek yaratılmasını sağlar. Bu desen sayesinde nesneler, kaynaklar gereksiz yere meşgul edilmeden yaratılırlar.
Kotlin’de bu design pattern’ı kullanmakta oldukça kolaydır. Örneğin Email
adında bir data class
‘ımız olsun,
data class Email(var recipient: String, var subject: String?, var message: String?)
Daha sonra bu Email
sınıfından bir nesne üretelim,
val mail = Email("abc@example.com", "Hello", "Don't know what to write.")
Bu işlemide gerçekleştirdikten sonra Email
sınıfından başka bir nesne üretmek istersek Prototype Design Pattern tam burada devreye girer. Prototype Design Pattern’a göre bu adımdan sonra yeni bir nesne üreterek kaynakları gereksiz meşgul etmek yerine var olan mail
nesnesinin kopyasını oluşturarak istediğimiz değişikleri yapmalıyız.
Kotlin’de bulunan copy
metodu bizim için nesneyi kopyalar. Bu metodun içine değiştirmek istediğimiz değişkenin adını ve alıcağı değeri yazarak kolayca Prototype Design Pattern’i gerçekleştirebiliriz.
val copy = mail.copy(recipient = "other@example.com")
Artık kaynakları gereksiz kullanmadan elimizde iki farklı nesne var. Sonuçlara aşağıdaki gibi bakabilirsiniz,
println("Email1 goes to " + mail.recipient + " with subject " + mail.subject)
println("Email2 goes to " + copy.recipient + " with subject " + copy.subject)
Sonuç
Hepsi bu kadar. Bu yazıda, Design Pattern’ların ilk bölümü olan Creatinal Patterns (Kurucu Desenler)’in ne işe yaradığını ve Kotlin’de nasıl ifade edildiklerini öğrendiniz. Design Pattern’ların devamı olan Structural Design Patterns ve Behavioral Design Patterns konularına yazılarımdan ulaşabilirsiniz.
Okuduğunuz için teşekkürler.