Kotlin — Design Patterns — Creatinal Patterns (Kurucu Desenler)

Ekrem Hatipoglu
KOUOSL
Published in
4 min readMar 17, 2018

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.

  1. Singleton Design Pattern
  2. Builder Design Pattern
  3. Factory Method
  4. Abstract Factory Pattern
  5. 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 PlantFactoryclass’ına OrangePlantreferans verilmiştir.Bu durumda PlantFactoryclass’ının ApplePlantüretmesi beklenemez ancak Factory Method’ta CurrencyFactoryclass’ı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.

--

--