Interface (Arayüz)

Javada soyutlama eylemini gerçekleştirmek için kullanılabilecek en güçlü gereçlerden bir tanesi interface ismindeki yapılardır. Interfaceler, yani arayüzler, bir anlamda şemalardır.

Binalardan oluşmuş bir site düşünün. Bu sitede farklı tipte daireler ve farklı tipte binalar olsun. Varsayalım ki x sayıda daire tipi ve y sayıda bina tipi mevcut olsun. Bir inşaat işinde, tahmin edersiniz ki, bu yapıların planları önceden kağıt üstünde çizilir. Yani farklı bina ve daire tiplerini tanımlayan şemalarımız vardır. Tersi yönden bakarsak, inşaat tamamlandıktan sonra bu şemalardan bir tanesini elinize alıp herhangi bir daireye girdiğinizde, planlara bakarak hangi daire tipinde olduğunuzu bilirsiniz. İşte bu şemalar tıpkı interfaceler gibidir. Yapılar ise bu durumda o interfacein implementasyonları olarak adlandırılır.

Bu konumuzda da daha evvel yaptığımız gibi yeni bir proje yaratacağız. Projemizin ismi 11 – Interfaces olacak. Projemizi yarattıktan sonra hiç vakit kaybetmeden bir interface yaratacağız. Interface oluşturmak tıpkı sınıf oluşturmak gibidir. Sadece açılan menüde Class yerine Interface seçeneğini belirleriz.

Eclipse ile interface yaratmak

Açılan pencerede, oluşturacağımız interface için bir isim vermemiz gerekecek. Burada da kurallar tıpkı sınıf isimlendirmelerinde olduğu gibidir. Ancak bir java dosyasının interface olduğunun kolay anlaşılması için kurumlar farklı farklı kültürleri takip ederler. Bazı kurumlarda interfaceler “I” harfi ile başlar ve implementasyon sınıfları ise baştaki “I” harfi olmadan kodlanır.

  • ICar.java -> Interface
  • Car.java -> Sınıf, implementasyon

Bazı kurumlarda ise interface isimleri doğrudan yazılırken, implementasyonlarının sonuna “Impl” son eki eklenir.

  • Car.java -> Interface
  • CarImpl.java -> Sınıf, implementasyon

Bir interface birden fazla implementasyon barındırıyorsa bu kuralları takip etmek zordur. Ancak bunların bir kültür meselesi olduğunu ve yukarıdaki isimlendirme biçimlerinin zorunlu olmadığını unutmayınız. Örneğin aşağıdaki gibi oluşturulmuş bir yapı da java tarafından problemsiz olarak çalıştırılabilecektir.ancak isimlendirme noktasında kötü bir tercih yapılmış olacaktır.

  • Lizard.java -> Interface
  • Panda.java -> Sınıf, implementasyon

Bu noktada, eğer tek implementasyon varsa, benim tercihim genellikle ikinci ilettiğim yöntemi izlemek oluyor. Lakin bu yaklaşımlar daha çok kültür meselesidir ve uymak zorunlu değildir.

Bu yazıda bir konu anlatımı gerçekleştiğinden, duruma uygun, yani anlamayı kolaylaştıracak isimler seçmeye çalışacağım. Böylece yarattığımız interface için “Vehicle” ismini kullanmaya karar verdim.

Eclipse ile interface yaratmak – II

Bir implementasyon yaratmadan önce biraz daha detaylara inelim. Interface yapıları bir anlamda sözleşmeler gibidir. Kendisini kullanan sınıfların neleri barındıracağını garantiler. Bu anlamda bir interface üzerinden oluşmuş sınıfların içini görmeden, o sınıfın içerisinde bulunan methodların varlığını garantilemiş olursunuz. Interfaceler de tıpkı sınıflar gibi java dosyalarıdır. Ancak interfacelerden, new anahtarı ile, doğrudan obje yaratılmaz.** Ayrıca bir sınıf başka bir sınıfı extend edebilirken; birden fazla interface bir sınıf tarafından implement edilebilir.

Bu bilgilerin üzerine yarattığımız interface içerisine iki adet method ekleyelim. Interfacelerin birer sözleşme veya şema olduğundan bahsetmiştik. O halde hayalimizdeki aracın bazı temel özelliklerini düşünelim.

  • Motoru çalıştırmak
  • Hareket etmek
  • Farları yakmak
  • Fren yapmak

Şimdi de yarattığımız interface içerisine bu özellikleri bir method imzası olarak yazacağız. Interfaceler içerisinde method gövdesi veya method bloğu ismini verdiğimiz, süslü parantezler arasında kalan kısım bulunmaz.** O halde işe koyulalım ve yukarıdaki method imzalarını, oluşturduğumuz interface içerisine ekleyelim.

**: Temel java ismindeki bir içerikte, bu durumun istisnalarından bahsetmeyi uygun görmüyorum.Ancak java 8 ile interfacelerin bu yapılarında bazı esneklikler mümkün kılınmıştır.

public interface Vehicle {

	//Motoru çalıştırmak
	//Geri dönüş değeri veya parametre yoktur
	public void runEngine();

	//Hareket etmek
	//Kaç birim gidildiğinin bilgisini alsın
	public void move(int move);
	
	//Farları yakmak
	//Aldığı parametreye göre ön veya arka farlar yanar
	public void turnOnLights(boolean isHeadlight);
	
	//Fren yapmak
	//Kaç birimde durulduğu bilgisi dönsün
	public int brake();
	
}

Görüldüğü gibi bir interface, yapı olarak sınıfla benzerlikler göstermektedir. Interface tanımlarken class ibaresi yerini interface sözcüğüne bırakır. Methodlar interface bloğunun içerisinde imza olarak tanımlanır. Methodların detayları verilmemektedir. Bunu da method bloklarının dosya içinde bulunmaması ve imzaların sonunda noktalı virgül bulunması ile gözlemleyebiliyoruz. Bir de interface ve sınıf ilişkisini konuşalım. İki sınıf arası uzantıyı extends sözcüğü gösteriyordu. Bir interface ile sınıf arasındaki uzantıyı ise implements sözcüğü gösterir. Gelin yeni tanımladığımız interface ile ilişkili, yani onun implementasyonu olan bir sınıf yazalım.

Vehicle arayüzünün implementasyonu olan Car sınıfı

Görüldüğü üzere yukarıdaki bilgi ile oluşturduğumuz sınıf hata vermektedir. Interfaceler için bir şablon, sözleşme benzetmesi yapmıştık. Bu sözleşmeye göre yukarıda listelediğimiz 4 methodun, sözleşmeye imza atan sınıflarda bulunması gerekiyor. Gelin bu sözleşmenin şartlarını yerine getirelim. Bunun için editörden ufak bir yardım alalım. Yukarıdaki görüntüde, satır numarasının yanındaki ampul ve çarpı işareti olarak gösterilen simgeye bir kere tıklıyoruz. Bunun ardından aşağıdaki gibi bir pencere ile karşılaşırız.

Eclipse ile eksik implementasyonların oluşturulması

Buradaki seçeneklerden “Add unimplemented methods” bize sözleşmemizin gereği ihtiyaç duyacağımız methodların bir şablonunu getirir. Bu seçeneği tıklıyoruz ve ardından aşağıdaki gibi bir sınıf oluşacaktır.

Otomatik olarak türetilen methodlar

Ancak biz bu methodları daha anlamlı olması için dolduracağız. İçine bir de ekstradan bir method daha ekleyeceğim. Buradan anlayacğaınız üzere implementasyonlar interface içerisindeki methodları barındırırken, bir yandan kendi methodlarına da sahip olabilirler. Hatta bir sınıf, hem bir interface implementasyonu hem de bir başka sınıfın alt sınıfı olabilir. Ancak biz bu örnekte sadece implementasyona odaklanacağız.

import java.util.concurrent.ThreadLocalRandom;

public class Car implements Vehicle {
	
	@Override
	public void runEngine() {
		System.out.println("Car.engine()");
	}

	@Override
	public void move(int move) {

		for (int i = 0; i < move; i++) {
			System.out.println("Car.move() -> " + i);
		}

	}

	@Override
	public void turnOnLights(boolean isHeadlight) {
		if (isHeadlight) {
			System.out.println("Car.turnOnLights() -> Headlights");
		} else {
			System.out.println("Car.turnOnLights() -> Tail Lights");
		}
	}

	@Override
	public int brake() {
		//10 ile 50 arasında random bir sayı dönüyoruz
		int distance = ThreadLocalRandom.current().nextInt(10, 50 + 1);
		return distance;
	}
	
	public void honk() {
		System.out.println("Car.honk()");
	}

}

Burada özellikle dikkatimizi çekmesi gereken bir konu var. Görüldüğü üzere interface üzerinden gelen methodların üstünde override ibaresi bulunurken, kendi tanımladığımız methodda bu yoktur. Arayüzlerden gelen methodları gövdelendirmek de bir override işlemidir.

TestClass sınıfımızı oluşturarak main içerisinde bazı aksiyonlar alacağız. Bu aksiyonları sizinle paylaşmak istiyorum.

public class TestClass {

	public static void main(String[] args) {

		Car testObject1 = new Car();
		testObject1.runEngine();
		testObject1.move(3);
		testObject1.turnOnLights(true);
		testObject1.brake();
		testObject1.honk();

		Vehicle testObject2 = new Car();
		testObject2.runEngine();
		testObject2.move(3);
		testObject2.turnOnLights(true);
		testObject2.brake();
		//testObject2.honk(); //hata verir
	}
}

Görüldüğü gibi testObject1 ve testObject2 isimlerinde iki adet nesne yarattım. Ancak bunların tanımlama anında bir farkları vardır. İlk objenin tipi tanımlama anında Car olarak verilmiştir ve Car sınıfına ait bir constructor ile yaratılmıştır. testObject2 ismindeki değişkenimiz ise tanımlama anında Vehicle ile tanımlanmış ve yine Car sınıfına ait bir constructor ile yaratılmıştır. Ancak bu şekilde yapılan bir tanımlama aynı zamanda kısıtlamaya yol açar. Bu kısıtlamanın sonucunda testObject2 sınıfında, Car sınıfına ait methodları kullanamazsınız. Kullanmanız mümkün olan tek methodlar interface içerisinde belirtilmiş olanlardır.

Şimdi elektrikli araçları temsil edecek bir interface daha oluşturalım.bu interface içerisinde şarjın dolmasını ve boşalmasını temsil eden iki adet method imzası yazacağız.

public interface ElectricVehicle {

	public void charge();
	
	public void discharge();
}

Şimdi ise ElectricCar isminde bir sınıf yazacağız. Odağımız elektrikli bir arabanın hem Vehicle özelliklerini taşıması hem de ElectricVehicle özelliklerini taşımasıdır. Yani bir sınıf iki ayrı arayüzü implemente etmeli. Sınıfımız şu şekildedir.

public class ElectricCar implements ElectricVehicle, Vehicle {

	@Override
	public void runEngine() {
		// TODO Auto-generated method stub

	}

	@Override
	public void move(int move) {
		// TODO Auto-generated method stub

	}

	@Override
	public void turnOnLights(boolean isHeadlight) {
		// TODO Auto-generated method stub

	}

	@Override
	public int brake() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void charge() {
		// TODO Auto-generated method stub

	}

	@Override
	public void discharge() {
		// TODO Auto-generated method stub

	}

}

Görüldüğü gibi iki ayrı interface implemente edilmektedir. Java açısından bunun bir sorun olmadığını belirtmek isterim. Sınıf bloğuna bakacak olursak her iki arayüzün methodlarını görürüz.

Ancak bu isteğimizi gerçekleştirmenin ayrı bir yolu daha vardır. Örneğin; ElectricVehicle arayüzü, Vehicle arayüzünü extend edebilirdi. ElectricCar sınıfı ise ElectricVehicle interfaceini implemente edebilirdi. Bu durumda sonuç aynı olurdu. Bu durumda kodumuz taslak olarak şu şekilde olurdu.

public interface Vehicle {
	//Sadece Vehicle interface methodları
}

public interface ElectricVehicle extends Vehicle {
	//Sadece ElectricVehicle interface methodları
}

public class ElectricCar implements ElectricVehicle {
	//Hem Vehicle hem ElectricVehicle arayüzünden 
	//gelen methodların implementasyonları
}

Görüldüğü gibi farklı bir biçimle, nihayetinde aynı sonucu elde etmek mümkün. Gelin bunu bambaşka bir şekilde daha ifade edelim.

public interface Vehicle {
	//Sadece Vehicle interface methodları
}

public interface ElectricVehicle {
	//Sadece ElectricVehicle interface methodları
}

public class Car implements Vehicle {
	//Vehicle arayüzünden 
	//gelen methodların implementasyonları
}

public class ElectricCar extends Car implements ElectricVehicle {
	//ElectricVehicle arayüzünden gelen
	//methodların implementasyonları ve isteğe bağlı 
	//olarak Car sınıfından gelen override edilmiş 
	//methodlar (extends için override zorunluluğu yoktur)
}

Buradan da anlaşılacağı üzere üzere çok biçimliliği yakalamak mümkün. Ancak unutulmaması gereken altın kural, bir sınıf extends sözcüğü ile sadece bir sınıftan miras alabilirken, implements ile bir çok interface için implementasyon haline gelebilir. Interfaceler ise başka bir arayüzün implementasyonları olamazlar. Ancak başka bir arayüzü extend edebilirler. Hatta ve hatta extends sözcüğü bir interface içerisinde kullanılıyorsa, sınıflardan farklı olarak, birden fazla arayüzü aynı anda extend edebiliriz.

Şahsi tecrübeme istinaden, interface konusunun, temel javadaki en önemli konu olduğunu düşünüyorum. Özellikle mimari tasarım kalıplarında çok ciddi öneme sahip bu yapının temelden anlaşılması, ilerisi için çok çok önemli. Bu konuyu sınıf yapıları ile birlikte kavramak OOP’nin temellerini anlamak demektir. Çok biçimliliğin (Polymorphism) genel konsepti bu iki başlıktan geçer.

1 comment on Interface (Arayüz)

Leave a Reply

Your email address will not be published. Required fields are marked *