Visibility Modifiers, Encapsulation ve Property vs Field

Ömer Sungur
10 min readJul 19, 2023

--

Bir başka yazı içeriğinden herkese merhaba, bu yazıda Kotlin dili için en önemli konulardan biri olan property ve field kavramlarının ne olduğunu anlatacağız. “val number = 5” yazarak aslında bir değişken tanımlamadığımızı örneklerle açıklayacağım. Bu yapıları daha iyi anlayabilmek için visibility modifiers ve encapsulation konularına da hakim olmamız gerekiyor. Bu 3 kavram birbiri ile ilişkilidir diyebiliriz. Bu anlatımda, Kotlin dili ve Java dili ile ilgili sık sık bir kıyaslama yapacağız ki yazdığımız kodlar nasıl çalışıyor anlayabilelim. Bunun için Kotlin kodlarımızı decompile edip Java diline dönüştüreceğiz. Şimdi sırasıyla bu yapıları inceleyelim. Son olarak şunu da eklememiz gerekir ki bu konular neredeyse bütün iş görüşmelerinde sorulur.

Visibility Modifiers Nedir?

Encapsulation kavramını anlamamız için ilk olarak Visibility Modifiers kavramını bilmemiz gerekiyor. Bu yapıya access modifiers (erişim belirleyiciler) da denilebilir. Bu yapı, yazdığımız kodlardaki sınıf, fonksiyon, interface, constructor, propertyler ve onların setter yapılarının erişim düzeyini belirlemek için kullanılır. Bu belirleyiciler, bir bileşenin hangi kapsamda ve hangi kod bloklarında erişilebilir olduğunu kontrol etmeye yardımcı olur. Kotlin dilinde 4 adet erişim belirleyici vardır. Bunlar;

  • Public: Kısıtlama olmaksızın bir bileşenin (class, fonksiyon, değişken!!! vs.) herhangi bir yerden erişilebilir olmasını sağlar. Kotlin’deki yapılar default olarak publictir. İnterface, abstract class, enum, fonksiyon, değişkenler!!! ve daha birçok yapının önüne public yazarsak IDE’nin public yazısını gri renkte yazdığını görürüz. Çünkü bunlar zaten otomatik olarak publictir, yazılmasına gerek yoktur.
  • Internal: Yapıları, sadece aynı modül içerisinden erişilebilir hale getirir. Bir bileşenin projenin içinde kullanılmasını ve başka bir modül tarafından erişilememesini sağlar. Modül dediğimiz şey, bir projenin bileşenlerini ve kaynak dosyalarını düzenlemek ve organize etmek için kullanılan bir kavramdır. Kafalara tam oturmadıysa, package yapısının bir üst hali diye düşünebiliriz.
  • Protected: Belirtecine sahip olan yapılar, kendi sınıfında veya kullanıldığı sınıfı miras alan sınıflarda kullanılabilir. Bunun haricinde kullanılamaz. Örneklerini inceleyeceğiz.
  • Private: Private keyworduna sahip bileşenlere, sadece tanımlandığı sınıf içerisinden erişilebilir. (istisnalardan bahsedeceğiz)

Çok önemli bir not olarak: Fonksiyonlar, propertyler, sınıflar, objeler ve interfaceler top-level olarak tanımlanabilirler. Yani bu yapıları tanımlayabilmek için herhangi bir sınıfa veya main fonksiyona ihtiyacımız yok. Kotlin dosyasının olması bizim için yeterlidir. Java’da böyle bir özellik yoktur. Örneğin, fonksiyonlar ve değişkenleri!!! tanımlayabilmemiz için kesinlikle bir sınıf içerisinde olmamız gerekiyor fakat Kotlin için kt. uzantılı bir dosyada (Kotlin dosyası) direkt tanımlama yapabiliriz.

// TOP LEVEL TANIMLAMA
var color = "RED" // Kotlin dosyasında (kt. uzantılı) direkt tanımlama yapabiliriz.
fun changeColor() {
color = "BLUE"
}

Visibility modifiers için örnekler vererek daha da pekiştirelim.

class Car {
public val carName: String = "Mercedes"
}

fun main() {
val car1 = Car()
car1.carName
}

carName ifadesine, Car sınıfının nesnesini oluşturarak her yerden erişebiliriz. Peki ya bu ifadeyi protected yapsaydık?

Tabii ki de erişemezdik. carName ifadesine sadece, ya tanımlandığı sınıftan ya da bu sınıfı miras alan sınıfın içerisinden erişebiliriz.

open class Car {
protected var carName: String = "Mercedes"

fun chaneCarName() {
carName = "Audi"
}
}

class AnotherCar: Car() {
fun changeCarName() {
carName = "Ferrari"
}
}

AnotherCar sınıfı, Car sınıfını miras aldığı için içerisindeki protected değişkenlere!!! erişebilir veya direkt olarak tanımlanan sınıfın içinde kullanılabilir.

Kotlin’de top-level kullanım özelliğinden bahsetmiştik. Protected yapıların top-level kullanımına izin verilmez. Sebebine gelecek olursak, protected keywordünü alt sınıflarda da kullanmak için yazarız. Top-level bir kullanımda bir bileşen herhangi bir yapının child yapısı olamadığı için orada protected yazmanın hiçbir amacı yoktur.

Top-levelda protected bir bileşen tanımlanamaz

Private değişkenlerin sadece tanımlandıkları yerden çağrıldığını söylemiştik. Buradaki private olan carName’e ne main fonksiyonundan erişebiliriz ne de bu sınıfı miras alan bir sınıftan erişebiliriz.

class Car { 
private var carName: String = "Mercedes"

fun chaneCarName() {
carName = "Audi"
}
}

“Private değişkenlere tanımlandıkları yer haricinde hiçbir yerden erişilemez” ifadesi tam olarak doğru değildir. Bu durum reflection yapısı kullanılarak çürütülebiliyor. Reflection konusu başka bir yazının konusu olsun. Fakat nasıl eriştiğimizi kısaca koda dökelim.

class Car {
private var carName: String = "Mercedes"

fun chaneCarName() {
carName = "Audi"
}
}

fun main() {
val car1 = Car()

val carNameField = car1.javaClass.declaredFields[0].name
val carNameField2 = car1.javaClass.getDeclaredField(carNameField)
carNameField2.isAccessible = true
val carNameValue = carNameField2.get(car1)
println(carNameValue)
}

Bu şekilde, reflection yapısı ile private olan değişkenlere de erişebiliriz. Internal için bir örnek kod bloğu yazmamıza gerek yok çünkü bu keywordün farkını, farklı modüllerimiz olduğunda görebiliriz. Ek olarak, bir yapı private olarak top-level seviyede tanımlanırsa o dosya içerisinden o yapıya her yerden erişilebilir.

Temel düzeyde visibility modifiers için bilmemiz gerekenler bunlardı.

Bu konu için ek olarak birkaç bilgiye daha göz atalım. Buralar ekstra bilgi vermek amacıyla hazırlanmıştır. Direkt olarak enkapsülasyon başlığına atlayabilirsiniz.

Outer sınıflar, inner sınıflarının içindeki private değişkenler erişemezler.

Inner class with private property

Constructorlara da visibility modifier verilebildiğini söylemiştik. Constructorlar default olarak publictir. Eğer biz bu yapıları private olarak tanımlarsak ne bu sınıfların nesnesi oluşturulabilir ne de bu sınıflar başka sınıflar tarafından primary constructor ile miras alınabilir.

private constructor

Lokal değişkenler!!!, lokal fonksiyonlar, ve lokal sınıfların erişim belirleyicisi olamaz. Local dediğimiz ifade, fonksiyonun içinde tanımlanan veya bir kod bloğunun içinde tanımlanan ve sadece tanımlandığı kapsamda erişilebilen bir yapıdır. Fotoğrafta hata veren hiçbir yapının erişim belirleyicisi olamaz.

Burada fonksiyon parametresinde erişim belirleyici veremiyoruz fakat sınıf constructorlarında kullanabiliriz.

Son olarak, üst sınıflarda tanımlanan yapılar, erişim belirleyicilerini subclasslara yansıtırlar. Örneğin üst sınıfta protected bir ifade tanımlamışsak bu sınıfı miras alan ve bu ifadeyi kullanan yapının da erişim belirleyicisi protected olacaktır.

üst sınıflarda tanımlanan yapılar, erişim belirleyicilerini subclasslara yansıtırlar

Evet visibility modifiers konusu bitirirmiş olduk. Şimdi sırada enkapsülasyon var.

Encapsulation Nedir ve Neden Gereklidir?

Çoğu zaman, sınıf içinde tanımladığımız değişkenlere (bu sefer !!! koymadığıma dikkat edin :) az sonra neden olduğunu açıklayacağım) erişimi kısıtlamak isteriz ki güvenli bir sistem yaratalım. Encapsulation kısaca, bir değişkeni sınıf içinde private olarak tanımlamak fakat buna erişilecek fonksiyonu public yapmaktır. Bu fonksiyonlara getter ve setter fonksiyonları denir.

Getter fonksiyonu ile değişkenimize erişim sağlarız, setter fonksiyonu ile ise o değişkenin değerini değiştiririz. Java dilinde enkapsülasyon şu şekilde yapılır:

public class EncapsulationExample {

private String color = "RED";

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}
}

Değişkenimizi sınıf içerisinde private olarak tanımlıyoruz. Biz bu değişkene hiçbir yerden direkt olarak erişemeyiz. Bütün bu fonksiyon ve değişkenlerin sınıflar içinde tanımlandığını görüyoruz. Java’da top-level bir kullanım olmadığını söylemiştik.

encapsulation

Setter fonksiyonunu bunun için yazmıştık zaten. Get fonksiyonu ile de değişkenin değerini alabiliyoruz. Buradaki getter ve setter fonksiyonlarına property denir.

EncapsulationExample encapsulationExample = new EncapsulationExample();
encapsulationExample.setColor("BLUE");
System.out.println(encapsulationExample.getColor());

Şimdi en önemli kısma geliyoruz. Encapsulation yapısı için Java’da böyle uzun uzun getter setter fonksiyonlarını yazıyoruz. Çoğu zaman Kotlin’de bu fonksiyonları yazmadığımızı görürsünüz. Direkt olarak değişkeni!!! tanımlarız hiç sebebini düşündünüz mü? İşte burada property ve field kavramları işin içine giriyor.

Property ve Field Nedir?

Yazının ilk başında demiştik ki “val number = 5” ifadesi bir değişken değildir. Gerçekten de Kotlin’de bu tanımlamalar değişken değildir. Arka planda değişken diye tanımladığımız ifadelerle propertyler oluşturuyoruz. Bu yüzden dolayı Kotlin için her değişken dediğimde !!! ifadesini kullandım. Aslında onlar birer propertydir. Hiç düşündünüz mü, neden Kotlin dilinde bütün yapılar default olarak publictir? Bu durum, encapsulation yapısına bir sorun teşkil etmiyor mu, Kotlin’de encapsulation yok mu?

Tam aksine, Kotlin’de encapsulation yapısı daha da gelişmiş bir yapıda kullanılıyor. Demiştik ya hani biz aslında değişken tanımlamıyoruz onlar sadece bir property diye. Şimdi Kotlin’de bir kod yazalım ve bu kodu Java diline decompile ederek karşılığına bakalım. Bu arada Kotlin ve Java dilleri aynı makine kodunu üretir.

class Color {
var color = "RED"
}

Çok temel düzeyde bir sınıf oluşturdum ve bu sınıf içerisinde color diye bir property oluşturdum. Artık bunlara değişken demediğime dikkat çekerim. Şimdi de IDE üzerinden shift+shift kısa yolunu kullanalım ve açılan bar içinde show kotlin bytecode yazarak ilgili aracı açalım. Sağ tarafta açılan pencereden decompile butonuna basalım ve üstteki kodun Java çıktısını görelim. Üstteki gibi bir property yazdığımızda, arka planda assembly kodu üretilirken aşağıdaki yapı oluşturulur.

public final class Color {
@NotNull
private String color = "RED";

@NotNull
public final String getColor() {
return this.color;
}

public final void setColor(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.color = var1;
}
}

Şimdi anladık mı Kotlin’de kullanılan yapıların aslında property olduklarını. Dil alışkanlığı ile dediğimiz değişkeni tanımlıyoruz. Ardından arka planda o ifadenin getter setter fonksiyonları ve ek olarak bu yapının field’ı oluşturuluyor. Field dediğimiz şey private String color = “RED”; ifadesidir. Ha bu arada Kotlin dilinde tanımladığımız şeyler propertydir diyoruz ama bunu sadece biz demiyoruz. IDE bize bu ifadeyi zaten söylüyor.

property

Bakın, color için property yazıyor.

Bu konu o kadar önemlidir ki çoğu yapının temelini oluşturur. Örneğin var ve val farkını direkt olarak bu konu sayesinde anlayabiliriz. Bir val property oluşturduğumuzda onun set fonksiyonu oluşturulmaz. Bu yüzden de val propertylerin değeri hiçbir zaman değiştirilemez.

Şimdi her şeyi toplayalım ve bir özet yapalım. Kotlin’de var color = “RED” yazarak bir property oluştururuz. Arka planda bu bu yapının field’ı ve bu field’a ait getter setter propertyleri oluşturulur. val color = “RED” tanımlaması yapmış olsaydık sadece getter fonksiyonu yazılırdı. Setter fonksiyonu yazılmazdı çünkü biz bu properynin değerini değiştirilmez yapıyoruz. Getter ve setterlara hem fonksiyon hem de property deniyor. İkisi de kullanılabilir.

Detay bilgilere gelecek olursak;

Yazdığımız propertynin erişim belirleyicisi ne olursa olsun field her zaman private olarak oluşturulur. Property önüne yazdığımız erişim belirleyicisi ile aslında getter ve setter fonksiyonlarının erişimini belirleriz. Eğer protected bir property oluşturursak getter setter fonksiyonları da protected olur. Eğer private bir property oluşturursak bunun getter ve setter fonksiyonları hiç oluşturulmaz bile. Mantıken düşününce de private olarak getter ve setter oluşturmanın bir amacı yoktur zaten.

Hemen deneyelim. Default olarak public color propertyimiz vardı. Bunu private erişim belirleyicisi olarak tekrar yazıyorum.

private property
private property decompile Java

Görüldüğü gibi private propertynin getter ve setter fonksiyonları hiç yazılmıyor bile. Sadece field yapısı oluşturuluyor. Buradaki field dediğimiz şey private String color = “RED”; satırıdır. Bu arada field, backing field, variable, değişken diye isimlendirdiğimiz yapıların hepsi özünde aynı şeydir. Bunlar bellekte yer tutarlar. Bu kavramlar birbirleri yerine kullanılabilir.

Kotlin’de property - field gibi daha birçok konu arka planda çok farklı şekillerde işleniyor. Kotlin dilini iyi kavrayabilmek için Java diline az da olsa hakim olsak fena olmaz.

Bir property tanımladığımız zaman arka planda neler olduğunu biliyoruz. Peki kendimiz get ve set fonksiyonlarını yazabilir miyiz? Evet.

Bir property yapısına ait getter ve setter fonksiyonlarını Java’ya göre biraz farklı yazmamız gerekiyor.

Kotlin’de değişkenler fonksiyonlara değer olarak atanabiliyor. Burada get fonksiyonunu yazdık ve field dediğimiz yapıya ulaşmış olduk. Field, değişkenin değeri ne ise bize onu verir. Burada set fonksiyonunu da yazdık ve bunu private yaptık. İşte bu yüzden visibility modifiers ve encapsulation konularını anlattık. Burada neler gerçekleştiğini zaten anlayabiliyoruz. Get fonksiyonu default olarak publictir ve biz bu propertynin değerini direkt olarak alabiliriz anlamına geliyor fakat set fonksiyonu private olarak tanımlandı. Bu da demek oluyor ki biz bu değişkenin değerini değiştiremeyiz. Buradaki set fonksiyonunda mantık şöyle çalışıyor: value diye bir değer al ve bunu şu an ki değer (field) ile değiştir, artık yeni değer value. Bu arada böyle get ve set yazacaksak bu fonksiyonların içinde bir iş mantığı olmalı yoksa boşuna yazmış oluruz çünkü bunlar zaten arka planda oluşturuluyor.

Getter ve setter fonksiyonlarının da erişim belirleyicisi olabilir. Burada bilmemiz gereken şey şudur: Get fonksiyonunun erişim belirleyicisi, propertynin erişim belirleyicisi ile aynı seviyede olmalıdır (Getter visibility must be the same as property visibility). Set fonksiyonu ise propertynin erişim belirleyicisi ile ya aynı olmalı ya da daha düşük seviyede olmalıdır.

get ve set fonksiyonları erişim belirleyicisi ile kullanılacaksa kurallar vardır

Son olarak bu getter ve setter fonksiyonlarını Java’daki gibi tanımlarsak hata alacağımızı belirtmek istiyorum.

JVM’deki fonksiyonun aynısı

Sebebini de artık biliyoruz. Bu fonksiyon arka planda zaten oluşturuldu :). Hata da bize bunu söylüyor. Bu fonksiyonlar zaten var diyor.

JVM signature error

Bunca gizlilik durumunu propertyler için anlattık. Peki fonksiyonları nasıl güvenli yazacağız? Fonksiyonlar için getter ve setter yapıları oluşturulmaz. Bunun iş mantığını kendi elimizle kurarız. Şöyle bir yapıya ihtiyacımız var:

  • İçerisinde iş mantığı olan fonksiyonumuzu private olarak tanımlarız.
  • Bu fonksiyona ek olarak bir public fonksiyon daha oluşturulur.
  • Public fonksiyonun içinden private fonksiyon çağrılır.
  • Artık public fonksiyonumuz nerede çağrılırsa çağrılsın sadece private fonksiyonun ismi görünür. İçeriği gizlidir.
class BankAccount {

private fun secretBusiness() {
// Business Logic
}

fun callSecretBusiness() {
secretBusiness()
}
}

fun main() {

val bankAccount = BankAccount()
bankAccount.callSecretBusiness()
}

Küçük bir not olarak: Yazının içeriğinde sürekli olarak değişken sandığımız şeylerin bir property olduğunu söyledik. Fakat çoğu kullanımda bunlara ağız alışkanlığı ile değişkenler deriz. Property ifadesi çok kullanılmaz. Siz değişken dendiğinde o şeyin property olduğunu anlayın ama konuşma sırasında değişken diye ifade edilidiğini unutmayın. :)

Yazının sonuna geldik. Uzun bir yazı oldu ama umarım yararlı olmuştur :). Property ve field konularını ve bunları daha iyi anlayabilmemiz için encapsulation + visibility modifiers konularını inceledik. Bu kaynağı hazırlarken Kotlin resmi dökümantasyonundan ve Gökhan Öztürk’ten (KeKod) yararlandım. Gökhan Öztürk’e ayrıca teşekkür ederim.

Bana Linkedin üzerinden ulaşabilirsiniz.

Son Güncelleme: 29.08.2023

--

--