C# programlama dilinden tanıdığımız Extension Method’ları, Kotlin de destekliyor. Extension’lar sayesinde (1) yeni bir türetilmiş tip yaratmadan (extend etmeden) (2) mevcut tipi değiştirip tekrar derlemeden, mevcut tiplere yeni özellikler ekleyebiliriz.

Aşağıdaki örnekte java.util.Date sınıfı için yazılmış isSunday() extension’ı var:

fun Date.isSunday(): Boolean {
    val calendar = Calendar.getInstance()
    calendar.timeInMillis = time
    return calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY
}

fun main(args: Array<String>) {
    val Subat_4_1979 = SimpleDateFormat("dd/MM/yyyy").parse("04/02/1979")
    if (Subat_4_1979.isSunday()) {
        println("Bir pazar gunu dogmusum.")
    }
}

Kotlin derleyicisi bir final sınıf yaratıp bu sınıfa java.util.Date tipinde parametre alan, boolean tipinde dönüş değeri olan isSunday adında static bir metod ekler. Java dünyasında, extension metodların karşılığı static metodları olan yardımcı (Util) sınıflardır. Yukarıdaki örneğin Java ile yazılmıs birebir karşılığı aşağıdaki DateUtil sınıfıdır.

public class DateUtil {
    public static boolean isSunday(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(date.getTime());
        return calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY;
    }

    public static void main(String[] args) {
        try {
            Date Subat_4_1979 = new SimpleDateFormat("dd/MM/yyyy").parse("04/02/1979");
            if (DateUtil.isSunday(Subat_4_1979)) {
                System.out.println("Bir pazar gunu dogmusum.");
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

Extension metodlarını kullanmak bir best practice midir emin değilim. Faydalarını ve dikkat etmemiz gereken noktaları birer birer tartışalım.

Code Completion

Static tipli programlama dilleri için geliştirilmiş IDE’ler yazılım sürecinde inanılmaz kolaylıklar sağlıyorlar. IntelliJ’de ayarlardan (Editor->General->Code Completion) Code Completion özelliğini kapatıp yarım saat kod yazmayı deneyin ne demek istediğimi anlarsınız.

isSunday örneğinden gidecek olursak, extension metod yerine yardımcı sınıf ve static metod kullandığımızı düşünelim. isSunday() metodunu kullanabilmek için önce bu metodun hangi sınıfta tanımlanmış olduğunu bulmamız gerekir. DateUtil, DateUtils, DateHelper, Dates aklıma gelen birkaç sınıf ismi. Ancak doğru sınıfı bulduktan sonra IDE’nin Code Completion özelliğini kullanabiliriz.

Extension metodu kullanırsak IDE bize aşağıdaki resimdeki gibi bir kıyak yapabilir. Çünkü isSunday metodu rastgele bir yardımcı sınıfın static metodu değil Date sınıfının bir extension metodudur.

intellisense

Static dispatch

java.sql.Date, java.util.Date sınıfından türetilmiş bir sınıftır. Sizce aşağıdaki kod bloğu çalıştırılırsa ekrana ne yazar?

fun java.util.Date.hhmm(): String {
    return "java.util.Date ${SimpleDateFormat("HH:mm").format(this)}"
}

fun java.sql.Date.hhmm(): String {
    return "java.sql.Date ${SimpleDateFormat("HH:mm").format(this)}"
}

fun main(args: Array<String>) {
    val date1 : java.util.Date = java.sql.Date(System.currentTimeMillis())
    println(date1.hhmm())

    val date2 : java.sql.Date = java.sql.Date(System.currentTimeMillis())
    println(date2.hhmm())
}

date1 ve date2‘nin runtime tipi aynı (java.sql.Date) olmasına rağmen yukarıdaki kodun çıktısı şöyledir:

java.util.Date 14:55
java.sql.Date 14:55

Bunun sebebi extension metodların dispatch edilmesinin dinamik değil statik olmasıdır. Bir başka deyişle derleyici, hangi extension metodu çalıştıracağına date1 ve date2‘nin derleme sırasında tanımlı olan tiplerine bakarak karar verir.

‘Member’ fonksiyonlar her zaman kazanır

Mevcut sınıf metodu ile aynı isme, aynı sayıda ve tipte parametreye sahip bir extension metodu yazarsanız sizin metodunuz değil ‘member’ metod çağırılır.

3rd party bir sınıf için extension metodu kullandığımızı düşünelim. Bu sınıfa ilerleyen versiyonlarda extension metodumuz ile aynı imzaya sahip bir metod eklenirse artık bizim metodunuz değil sınıfın gerçek metodu çağırılmaya başlanır ve bundan haberdar olmamız neredeyse mümkün değildir.

Extension metod’ları nasıl organize etmek lazım?

Extension metod’ları ayrı bir dosyaya ya da kullanmak istediğiniz bir sınıf dosyasının içine yazabilirsiniz. Düzenli olması için benim tercihim

  • net.peakgames.extensions.libgdx
  • net.peakgames.extensions.json
  • net.peakgames.extensions.collections

gibi kendi package’ları altında (namespace) tanımlamak.

@file:JvmName("LibGDXExtensions")
package net.peakgames.extensions.libgdx

import com.badlogic.gdx.scenes.scene2d.Actor

fun Actor.setOpacity(opacity: Float) {
    color.a = opacity
}

Yukaridaki örnekte LibGDX Actor’leri için basit bir extension metod tanımı var. Bu extension’ı net.peakgames.extensions.libgdx paketi altına LibGDXExtensions dosyasına yazıyoruz ve kullanmadan önce aşağıdaki gibi import etmemiz gerekiyor:

import net.peakgames.extensions.libgdx.opacity
...
actor.opacity(0.3f)

LibGDXExtensions dosyasının başına yazdığımız @file:JvmName annotation, Kotlin derleyicisinin LibGDX extention’ları için oluşturacağı sınıfın ismini belirtmek için kullanılıyor. @file:JvmName belirtmeseydik Kotlin LibgdxExtensionsKt adında bir sınıf oluşturacaktı. Yukarıda yazdığımız opacity metodunu Java’dan aşağıdaki gibi çağırabiliriz.

import net.peakgames.extensions.libgdx.LibGDXExtensions;
...
LibGDXExtensions.opacity(actor, 0.3f);

Sonuç olarak dikkatli ve abartmadan kullanıldığında Extension metodlar, kodun daha okunaklı olmasına yardımcı oluyorlar. Şunu hiçbir zaman aklımızdan çıkartmamamız lazım:

With Great Power Comes Great Responsibility