Erişim Düzenleyiciler

Getter ve setter konumuzdan sonra, javadaki başka bir önemli kavrama da değinmek istiyorum. Kod bloklarımızda sıklıkla gördüğümüz public anahtar sözcüğünün ne işe yaradığını, bu sözcüğün alternatiflerini ve bütün bu alternatiflerin ne anlama geldiğini bu yazıda ele alacağız.

Öncelikle encapsulation kavramına değinmek gerekiyor. Zira bir önceki konumuzda bazı eksiklerimiz olduğunu söylemiştik. Encapsulation, yani sarmalama, nesneye yönelik programlamanın en temel kavramlarındandır. Bu kavramı, temel olarak bilginin uygun şekilde saklanması olarak düşünebiliriz. Peki ama neden bir bilgiyi saklamak isteyelim ki? Bunun sebebine aslında getter ve setter methodlarımızda değinmiştik. Ancak bu yazıda farklı bir örnekle temsil etmek istiyorum.

Bir markete gittiğimizi düşünelim. Aldıklarımızı kasaya getirdiğimizde kasiyer bunların barkodunu okutur ve sistem otomatik olarak fiyatları size gösterir. Ancak müşteri olarak bizim fiyatlara müdahale yetkimiz yoktur. Yani 100 TL’lik ürünün fiyatını müşteri olarak 50 TL olarak değiştiremeyiz. Ancak bunu marketin personeli değiştirebilir. Belki her personel bu yetkiye sahip olmayabilir. Görüldüğü üzere bazen bir değeri setlemek çeşitli ayrıcalıklara sahip olmayı gerektiriyor. Aynı durum 100 TL’lik ürünün markete geliş fiyatını görmek istediğimizde de geçerli olacaktır. Yani bazı değerleri getlemek için ayrıcalık sahibi olmalısınız.

Bu durumu önceki yazımızdaki örnek üzerinden tekrar değerlendirirsek; bir ApartmanDairesi nesnesinin fieldlarına getter veya setter kullanmadan ulaşılamaz olmalıdır. Ancak önceki örneğimizin main methodunu hatırlarsak, hem getter/setter kullanılan insaat2() hem de kullanılmayan insaat() methodları çalışmaktaydı. Oysa biz insaat2() methodunu fieldların denetimini sağlamak için yazmıştık. İşte bu denetimi zorunlu kılmanın bir yolu var: Doğru erişim düzenliyici kullanmak!

Bu yazımız için 03 – AccessModifiers isminde bir proje oluşturalım. Bu defa öncelikle TestClass sınıfımızı oluşturacağım. Bu bilgi bundan sonraki tüm projelerimizde de geçerli olacaktır. Bununla birlikte, market örneğinden yola çıkarak bir sınıf kodlayalım. Bu sınıf üzerinde fiyat ve isim tutan bir ürünü temsil etsin ve ismi de Urun olsun.

public class Urun {

	public int fiyat;
	
	public String isim;
	
}

Main methodumuzun içerisinde bu sınıfı kullanarak bir nesne yaratalım. Temsili olarak bir sakızı düşünebiliriz. İsmi sakiz ve tipi/sınıfı Urun olan bir değişken yaratalım. sakiz’in fiyat değeri 1 ve isim değeri de çiklet olsun. Nihayetinde bu objenin fiyat ve ismini konsola yazdıralım.

public class TestClass {

	public static void main(String[] args) {

		Urun sakiz = new Urun();
		sakiz.fiyat = 1;
		sakiz.isim = "çiklet";
		
		System.out.println(sakiz.fiyat);
		System.out.println(sakiz.isim);
	}
	
}

Yukarıdaki örnekte görüldüğü üzere, sakiz nesnesinin fiyatını direkt olarak değiştirebiliyoruz. Aynı şekilde fieldların değerini hiçbir denetime tabi olmadan okuyabiliyoruz. Şimdi bir düzenleme daha yapıyoruz ve Urun sınıfımızdaki getter ve setterları ekliyoruz.

public class Urun {

	private int fiyat;
	
	public String isim;

	public int getFiyat() {
		return fiyat;
	}

	public void setFiyat(int fiyat) {
		this.fiyat = fiyat;
	}

	public String getIsim() {
		return isim;
	}

	public void setIsim(String isim) {
		this.isim = isim;
	}
	
}

Yukarıdaki kodu dikkatli incelerseniz bir durumun farkına varacaksınız. Getter ver setterları ürettikten sonra bir düzenleme daha yaptım ve fiyat fieldının önündeki public anahtar sözcüğünü private ile değiştirdim. Bu değişikliği siz de sağladığınızda editörünüzde bir hata aldığınızı göreceksiniz.

Buradaki hatayı incelerseniz (imleci kırmızı çizili yere getirir veya ampül işaretine tıklarsanız) fiyat alanının görünü olmadığına dair bir uyarı göreceksiniz. Bu durumu aşmak için fiyatı set ettiğim yerde setter ve get ettiğim yerde getter kullanmalıyız. Kodumuzu aşağıdaki gibi düzenliyoruz.

public class TestClass {

	public static void main(String[] args) {

		Urun sakiz = new Urun();
		sakiz.setFiyat(1);
		sakiz.isim = "çiklet";
		
		System.out.println(sakiz.getFiyat());
		System.out.println(sakiz.isim);
	}
	
}

Bu değişikliği yaptığımızda getter ve setter kullanımını zorunlu hale getirmiş olduk. Peki tam olarak neler oldu? Bizim burada yaptığımız şey bir fieldın erişim seviyesini değiştirmek oldu. Javada bunu yaparken fieldların, methodların ve hatta sınıfların tanımlarının önünde kullanabileceğimiz, 4 adet erişim düzenleyicisi bulunmaktadır.

public : Erişimin her yerden sağlanabildiğini gösterir. Diğer sınıflar, diğer paketler bu erişim düzenliyicisi ile tanımlanmış methodlarınıza, fieldlarınıza ve sınıflarınıza ulaşabilir. Güvende kalmak için uygulama düzeyinde gerekmedikçe public sözcüğünü kullanmamalıyız.

protected : Önüne geldiği varlığı, bulunduğu paket, alt sınıflar ve sınıfın kendi içinden çağrılabilir halde tanımlamak için kullanılır. Buradaki alt sınıf ve paket kavramı kafanızı kurcalamasın. Bunların bir kısmına ilerleyen dönemde değineceğiz.

boş erişim düzenleyici : Diğer 3 erişim düzenleyiciyi kullanmadan tanım yapmak anlamını taşır. Mesela Urun sınıfımızdaki isim fieldının önündeki public sözcüğünü silersek boş erişim düzenliyicisi ile bu alanı tanımlamış oluruz. Boş erişim düzenleyicisi kullandığınız varlıklar alt sınıflardan ve diğer sınıflardan direkt olarak çağırılamaz. Ancak paket içerisinden erişilebilir durumdadır.

private : En sıkı erişim düzenleyicisidir. Önüne konulduğu java elemanını sadece o sınıf içerisinden erişilebilir hale getirir. Bu erişim düzenleyicisi parmağa bağlanan bir ip gibi aklınızda olmalı, bütün varlıklarınızı öncelikle private tanımlamayı düşünmelisiniz.

sınıfpaketalt sınıfdiğer sınıflar
publicVarVarVarVar
protectedVarVarVarYok
boşVarVarYokYok
privateVarYokYokYok
Erişim Tablosu

Örneğimize dönecek olursak; fiyat alanını private yapmamız sebebiyle alana direkt erişim yetkisi sadece sınıfın kendisinde kalmış durumda. Dolayısı ile farklı bir sınıftan (TestClass) buraya erişim sağlanamıyor. Ancak bu alanın setter ve getterları public olduğundan ve biz bunlara ulaşabildiğimizden, dolaylı olarak fiyat bilgisine erişebilmekteyiz. Yani fiyat bilgisine erişim denetim altına alınmış durumda. Ancak isim bilgisi için aynı şey geçerli değil. İsim public olarak tanımlanmış bir field olduğundan direkt erişim mümkün ve değeri değiştirilebilir ve bir denetime tabi olmadan okunabilir durumda.

Bu noktada artık farklı önlemler alabilir durumdayız. Diyelim ki fiyat girişi yapan personel kazara fiyatı 0 girdi. Setter methodumuz içerisinde gelen bu fiyatın kontrolünü yapabilir durumda olduğumuzdan; “Eğer fiyat sıfır girildiyse hata ver” ya da “İsmi olmayan ürünün adını ‘Tanımsız’ olarak listele” gibi daha programatik kontrolleri sağlayabilir halde oluruz.

Son iki yazımızla birlikte encapsulation konusuna değinmiş olduk. Özetle encapsulation sayesinde alanlarımızı sadece-okuma (read-only), sadece yazma (write-only) gibi biçimlerde kullanmamız mümkündür. Ayrıca veri okumayı ve yazmayı daha kontrollü bir hale getirir. Uygulamanızı daha güvenli kılar ve bakım süreçlerini kolaylaştırır. Hata ayıklama süreçlerinde işimizi kolaylaştırır. Kodumuzun yeniden kullanılabilir olmasını sağlar. İlerleyen yazılarla birlikte bunların hepsini yavaş yavaş bir arada kullanabilmeye başlayacağız. Kendinize bir ödev olarak önceki projeleri getter ve setterlı halde yazmayı alabilirsiniz. Pratik sizi daha iyi bir noktaya taşıyacaktır. Bir sonraki yazıda görüşmek üzere.

Getter ve Setter

Son yazımızda KaraAraclari sınıfımızda bulunan bazı methodların kullanımını ve bunların birbiri ile ilişki içerisinde nasıl hareket edebileceğini görmüştük. Sınıf yapılarına dair yazılarımızın genelinde ayrıca constructor kullanımına da odaklanmıştık.

Bu yazımızda yine sınıf yapılarının bir parçası olarak kabul edilen, getter ve setter ismi verilen methodlardan bahsedeceğiz. İsimlerinden yola çıkarak bu methodların bazı değerleri getirdiğini (getter) ve bazı değerleri güncellediğini (setter) söyleyebiliriz. Yazılım dünyasında bu methodların yaptığı işlere “get etmek” ve “set etmek” ismi verilmektedir.

Bildiğiniz gibi sınıfların ana yapılarından bir tanesi, o sınıfta bulunan fieldlardır. Daha evvel yazdığımız sınıfımızda iki adet fieldımız vardı. Kısaca bunları hatırlamak gerekirse aşağıdaki kod parçasına göz atabilirsiniz.

	public int koltukSayisi;
	
	public String renk;

Sürekli aynı proje üzerinden devam etmemek için, gelin bu yazımız için yeni bir proje oluşturalım. Bu yazımızda ele alacağımız projemizin ismini 02 – GettersAndSetters olarak belirleyelim. Editörümüzün sol tarafındaki kısma baktığımızda şöyle bir görüntüyle karşılaşacağız.

Getter ve setter örneğimiz için, yeni bir sınıf daha yaratmamız gerekiyor. Bu tarz durumlarda, çok klişe olarak 4 işlemden sorumlu bir sınıf yazılır. Ben mümkün olduğunca bundan uzak durmak istiyorum. Java sınıflarını gerçek hayattaki objelere benzetmek, OOP mantığını güçlendirecek bir hareket olacaktır. Eğer OOP konusunda kendinizi yeterli hissetmiyorsanız önce elle tutulabilir örnekleri düşünmeniz yararlı olacaktır. Matematik işlemleri, soyut kalmaları sebebiyle, bu anlattığım duruma uymamakta. Dolayısı ile biz farklı bir örnek ile ilerleyeceğiz.

Gelin bugün bir inşaat işi üzerine düşünelim. Diyelim ki bir apartman inşa edilecek. Bu apartmandan daire almak isteyen farklı profilde insanlar var. Dolayısı ile farklı dairelere ihtiyacımız var. Her bir daireyi bu işin içerisindeki bir nesne olarak düşünebiliriz. Bir apartman dairesi denildiğinde aklımıza gelen şeyleri düşünelim. Bir kapıdan içeri gireriz, duvarları boyalı, odaları sayılı olan, belki iki katlı olan, bazen açık mutfaklı olan bir yapıdır. Buradaki özellikleri düşündüğümüzde çok fazla madde çıkabilir durumdadır. Gelin bunların kısıtlı bir kısmına odaklanalım. Projemizde ApartmanDairesi isminde bir sınıf yaratıp bu örnek için kullanmaya başlayalım. Bizim apartman dairemizi bir duvar rengi, oda sayısı ve çelik kapılı olup olmaması temsilediyor olsun.

public class ApartmanDairesi {

	public String duvarRengi;
	
	public int odaSayisi;
	
	public boolean celikKapiliMi;
	
}

Uygulamamız, inşaata söz konusu olan her bir apartmanın java nesnesini yaratacak olsun. Yani apartmanımızdaki farklı farklı daireleri yukarıdaki sınıf ile oluşturacağız. Bu işi de önceki örneğimizde olduğu gibi TestClass isminde, main methodunu barındıran bir sınıf ile yapacağız. 4 adet daire oluşturuyor ve bunların fieldlarını dolduruyoruz.

public class TestClass {

	public static void main(String[] args) {
		
		ApartmanDairesi daire1 = new ApartmanDairesi();
		daire1.celikKapiliMi = true;
		daire1.duvarRengi = "Mavi";
		daire1.odaSayisi = 3;
		
		ApartmanDairesi daire2 = new ApartmanDairesi();
		daire2.celikKapiliMi = false;
		daire2.duvarRengi = "Gri";
		daire2.odaSayisi = 3;
		
		ApartmanDairesi daire3 = new ApartmanDairesi();
		daire3.celikKapiliMi = true;
		daire3.duvarRengi = "Mavi";
		daire3.odaSayisi = 2;
		
		ApartmanDairesi daire4 = new ApartmanDairesi();
		daire4.celikKapiliMi = true;
		daire4.duvarRengi = "Yeşil";
		daire4.odaSayisi = 1;
		
	}
}

Main methodumuzu çalıştırdığımızda 4 adet nesne oluşturmuş olacağız. Bunlar gerçek dünya gözüyle bakıldığında birbirinden farklı özelliklere sahip 4 adet apartman dairesidir. Java diliyle konuşursak; ApartmanDairesi sınıfına ait 4 farklı instance/nesne yaratmış durumdayız.

Varsayalım ki inşaat bitti ve dairelere taşınmalar başladı. Ancak daire2‘nin sahibi eski kararından pişman oldu ve çelik kapı taktırmak istedi. Bu durumda programatik olarak yapmamız gereken daire2 nesnesine ait celikKapiliMi fieldını true olarak güncellemektir. Gerçek hayatta yapılacak şey ise kapının sökülüp yenisinin takılmasıdır.

daire2.celikKapiliMi = true;

Bu defa tersten düşünelim. 3 odalı bir eviniz var ve bunu 4 odalı yapmak istiyorsunuz. Bir odanızı ikiye bölmediğinizi düşünürsek 4. bir odayı inşa etmeniz söz konusu olamamaktadır. Ancak programatik olarak 3 odalı olan daire1 nesnemize bunu yapabilmemizin önünde bir engel yoktur. Kısacası aşağıdaki kodu main methodumuza yazdığımızda çalıştığını görürüz.

daire1.odaSayisi = 4;

Demek ki java dünyasında, tıpkı gerçek hayatta olduğu gibi, bazı aksiyonları denetleme ihtiyacı duymaktayız. Yani duvarları boyamak bir ustanın varlığına, eve oda eklemek bazı fiziksel engellere tabidir. Şu ana kadar bir değeri setlemek üzerine konuştuk. Bazı durumlarda ise getlerken denetime ihtiyaç duyabiliriz. Örneğin daire sahipleri evinin rengini paylaşmak istemiyor olabilir. Bu durumda aşağıdaki kodun çıktı vermemesini sağlamak isteriz.

System.out.println(daire1.duvarRengi);

Bütün bu durumları kontrol altında tutmak için field değerlerinin okunması ve yazılması esnasında yardımcı methodlar kullanabiliriz. Bunlara getter ve setter methodlar ismi verilmektedir. ApartmanDairesi sınıfımızın fieldları için getter ve setter methodları oluşturalım. Sınıfımızı aşağıdaki gibi düzenliyoruz.

public class ApartmanDairesi {

	public String duvarRengi;
	
	public int odaSayisi;
	
	public boolean celikKapiliMi;

	public String getDuvarRengi() {
		return duvarRengi;
	}

	public void setDuvarRengi(String duvarRengi) {
		this.duvarRengi = duvarRengi;
	}

	public int getOdaSayisi() {
		return odaSayisi;
	}

	public void setOdaSayisi(int odaSayisi) {
		this.odaSayisi = odaSayisi;
	}

	public boolean isCelikKapiliMi() {
		return celikKapiliMi;
	}

	public void setCelikKapiliMi(boolean celikKapiliMi) {
		this.celikKapiliMi = celikKapiliMi;
	}
	
}

Dikkat edilirse getter methodlar (ismi get ile başlayan) geriye o fieldın tipinde dönüş sağlarken, setter methodlar geriye değer dönmüyor. Getter ve setter methodların olağan davranışı bu şekildedir. Tekrar koda dikkat ederseniz, setter methodlar parametre alırken, getter methodlar almamaktadır. Gerçek hayatta da, evinizin duvar rengini görmek için ek bir parametreye ihtiyaç duymazsınız. Ancak evinizin duvarını boyamak için hangi renk boyayı istediğinizi belirtmek durumundasınızdır. Yani bir değeri setliyorsam yeni değeri belirtirim. Ancak bir değeri getliyorsam değer zaten oradadır ve parametre vermem.

Bu noktada bir de kısayoldan bahsetmek istiyorum. Çoğu editör, getter ve setter üretimi için kısayollar barındırmaktadır. Eclipse ile getter ve setter üretmek için sınıfınızın içerisinde boş bir yerde sağ tıklayın. Açılan menüde source kısmına gelip Generate Getters and Setters menüsüne tıklayınız.

Açılan pencerede getter ve setter üretmek istediğiniz tüm fieldları işaretleyip, Generate butonuna tıklarsanız, editör tüm getter ve setterları sizin için sınıfınızın içerisine ekleyecektir.

Getter ve setterları ekledikten sonra test sınıfımızı da değiştiriyoruz. Field değerlerine direkt ulaşım yerine getter ve setterları kullanacağız. Programımızı uygun şekilde değiştireceğiz. Ancak karşılaştırma yapmayı kolaylaştırmak için Programın ilk halinde bulunan akışı insaat() isminde bir methoda alacağım. insaat2() ismindeki methodda ise getter setter kullanımını göstereceğim. Test sınıfımızı aşağıdaki gibi değiştiriyoruz.

public class TestClass {

	public static void main(String[] args) {
		
		insaat();
		insaat2();
		
	}

	private static void insaat2() {
		
		ApartmanDairesi daire1 = new ApartmanDairesi();
		daire1.setCelikKapiliMi(true);
		daire1.setDuvarRengi("Mavi");
		daire1.setOdaSayisi(3);
		
		ApartmanDairesi daire2 = new ApartmanDairesi();
		daire2.setCelikKapiliMi(false);
		daire2.setDuvarRengi("Gri");
		daire2.setOdaSayisi(3);
		
		ApartmanDairesi daire3 = new ApartmanDairesi();
		daire3.setCelikKapiliMi(true);
		daire3.setDuvarRengi("Mavi");
		daire3.setOdaSayisi(2);
		
		ApartmanDairesi daire4 = new ApartmanDairesi();
		daire4.setCelikKapiliMi(true);
		daire4.setDuvarRengi("Yeşil");
		daire4.setOdaSayisi(1);
		
	}

	private static void insaat() {
		
		ApartmanDairesi daire1 = new ApartmanDairesi();
		daire1.celikKapiliMi = true;
		daire1.duvarRengi = "Mavi";
		daire1.odaSayisi = 3;
		
		ApartmanDairesi daire2 = new ApartmanDairesi();
		daire2.celikKapiliMi = false;
		daire2.duvarRengi = "Gri";
		daire2.odaSayisi = 3;
		
		ApartmanDairesi daire3 = new ApartmanDairesi();
		daire3.celikKapiliMi = true;
		daire3.duvarRengi = "Mavi";
		daire3.odaSayisi = 2;
		
		ApartmanDairesi daire4 = new ApartmanDairesi();
		daire4.celikKapiliMi = true;
		daire4.duvarRengi = "Yeşil";
		daire4.odaSayisi = 1;
		
	}
}

Burada peşpeşe iki method çağrısı yapmış bulunmaktayız. Çıktı olarak bu methodların aynı işi yaptığını söyleyebiliriz. Ancak görüldüğü üzere alanların değer değişiklikleri, insaat2() methodunda setterlar vasıtasıyla yapılmaktadır. Peki ilk haline göre neler değişti?

  • Bir değerin get edilmesini veya set edilmesini bir methoda bıraktık. Yani okunmasını ve yazmasını kontrol altına aldık. Buna encapsulation veya sarmalama/kapsülleme/paketleme ismi verilmektedir.
  • Bir değerin sınıf içerisinde farklı okunurken sınıf dışında farklı okunmasına imkan verdik. Örneğin inşaat sınıfı duvar rengi olarak tüm yeşil tonlarını dış sınıflara yeşil dönerken, sınıf içerisinde farklı tonlarla işlem yapabilir. Bir eve perde alırken duvar rengi ton olarak çok büyük mesele teşkil etmeyebilir. Ancak duvarın bir parçasını daha boyayacaksanız önemli olabilir. Bu yüzden bazı noktalarda ayrım, bazı noktalarda genel geçer cevaplar gereklidir.
  • Kodun debug edilmesi sırasında değer değişikliklerinin takibi için güvenilir bir nokta oluşturduk. Böylece değişikliğin tek sınıfın tek noktasında gerçekleştiğini garanti altına almış olduk.

Getter ve setter kullanımının yararları aslında burada görüldüğünden daha fazladır. Ancak bunu daha iyi yorumlamak için farklı bir konuyu da anlamaya ihtiyaç vardır. Bu konu ise access modifiers yani erişim düzenleyicilerdir. Her ne kadar yukarıdaki örnekte getter ve setterlar oluşturmuş olsak da, örneğimiz bu haliyle eksik kalmakta ve amaca tam olarak hizmet etmemektedir.

Bir sonraki yazımızda erişim düzenleyicileri konusuna değineceğiz ve yukarıdaki kodun neden eksik kaldığına dair örnekler yapacağız.