Ders 04 / 13
Soyut Sınıflar ve Arayüzler
Aynı yetenek sekiz ayrı yoldan kurulduğunda derleme üçünde duruyor; kalan beşte derleyici kararı kendisi veriyor ve tarafı hiç sormuyor — sınıf gövdesi arayüz varsayılanını yener, alt arayüz üsttekini yener, yalnız iki bağımsız arayüz aynı varsayılanı verdiğinde reddeder. Durum taşıyan bir yetenek arayüzle kurulamaz.
İçindekiler
Önceki ders sekiz kararı tek bir referans üzerinde okudu ve tablodaki ikinci satır bir
arayüzden geliyordu: Tasiyici arayüzünün varsayılan yöntemi de yeniden tanımlanabiliyor ve
kararı çalışma zamanı tipi veriyordu. Arayüzün kendisi ise yalnız bir satır olarak geçti.
Bu ders onu konu yapar. Java’da bir yetenek iki ayrı araçla kurulabilir: gövdesi eksik bir soyut sınıf ya da bir arayüz. İkisi de aynı işi görüyor gibi durur, ve seçim çoğunlukla bir tasarım tercihi diye anlatılır. Ölçüm başka bir şeye bakar: derleyici hangi kurulumu reddediyor, ve reddetmediğinde kararı kime bırakıyor?
Programlama Paradigmaları kursu arayüz ile soyut sınıf arasındaki seçimi tasarım gerekçeleriyle karşılaştırmıştı; o gerekçeler tekrarlanmaz. Burada sayılan şey derleyicinin durduğu noktalardır.
İki Araç, İki Ayrı Kısıt
Soyut sınıf bir sınıftır: alan taşır, yapıcısı vardır, gövdeli ve gövdesiz yöntemleri yan yana koyabilir. Karşılığında Java’nın tek üstlü kalıtımına bağlıdır — bir sınıf yalnız bir sınıfı genişletebilir.
Arayüz tersini yapar. Bir sınıf istediği kadar arayüzü gerçekleştirebilir, dolayısıyla yetenekler bir sınıfa yan yana takılabilir. Karşılığında arayüz örnek durumu tutamaz: alan bildirimleri örtük olarak sabittir. Varsayılan yöntemler arayüze gövde koyma imkânı verir, ama üzerinde çalışacak bir durum vermez.
Bu iki kısıt bir araya geldiğinde bir soru doğar. Çoklu gerçekleştirme serbestse, iki ayrı arayüz aynı imzayla gövdeli birer yöntem verdiğinde ne olur? Kalıtımda böyle bir durum yoktur, çünkü üst yalnız bir tanedir. Ölçüm buraya bakar.
Ölçüm Çekirdeği
Ölçümün birinci yarısı hiçbir şey çalıştırmaz: her kurulum bir kaynak metnidir, derleyici program içinden çağrılır ve yalnız çıkış durumu okunur.
- SN16 — Sekiz kurulumun hepsi aynı yeteneği kurar:
tasiadlı, dizgi döndüren tek bir yöntem. Değişen tek şey yeteneğin hangi tip biçimiyle verildiğidir. - SN17 — Derleyicinin ürettiği iletiler yutulur; okunan tek şey kaynağın kabul edilip edilmediğidir.
// Derle.java — verilen kaynağın derlenip derlenmediğini söyler
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.file.*;
import java.util.spi.ToolProvider;
class Derle {
static String olur(String kaynak) throws Exception {
Path d = Files.createTempDirectory("soyut");
Path k = d.resolve("Deneme.java");
Files.writeString(k, kaynak);
PrintWriter bos = new PrintWriter(Writer.nullWriter());
return ToolProvider.findFirst("javac").orElseThrow()
.run(bos, bos, "-d", d.toString(), k.toString()) == 0
? "derlendi" : "derlenmedi";
}
}
Sekiz Kurulum, Derleme Kaçında Duruyor
// Kurulum.java — aynı yetenek sekiz ayrı yoldan kuruluyor, kaçı derleniyor
public class Kurulum {
static final String[][] DENEME = {
{ "tek soyut sinifi genisletme",
"abstract class A { abstract String tasi(); }\nclass C extends A { public String tasi() { return \"c\"; } }" },
{ "iki soyut sinifi genisletme",
"abstract class A { }\nabstract class B { }\nclass C extends A, B { }" },
{ "soyut yontemi gerceklestirmeme",
"abstract class A { abstract String tasi(); }\nclass C extends A { }" },
{ "iki arayuzu gerceklestirme",
"interface A { default String tasi() { return \"a\"; } }\ninterface B { default String ad() { return \"b\"; } }\nclass C implements A, B { }" },
{ "iki arayuz ayni varsayilan, cozumsuz",
"interface A { default String tasi() { return \"a\"; } }\ninterface B { default String tasi() { return \"b\"; } }\nclass C implements A, B { }" },
{ "iki arayuz ayni varsayilan, sinif cozer",
"interface A { default String tasi() { return \"a\"; } }\ninterface B { default String tasi() { return \"b\"; } }\nclass C implements A, B { public String tasi() { return \"c\"; } }" },
{ "arayuz ile soyut sinif ayni yontem",
"interface A { default String tasi() { return \"a\"; } }\nabstract class S implements A { public String tasi() { return \"s\"; } }\nclass C extends S { }" },
{ "alt arayuz ustteki varsayilani ezer",
"interface A { default String tasi() { return \"a\"; } }\ninterface B extends A { default String tasi() { return \"b\"; } }\nclass C implements B { }" },
};
public static void main(String[] args) throws Exception {
System.out.printf("%-40s %s%n", "kurulum", "sonuc");
int durdu = 0;
for (String[] d : DENEME) {
String s = Derle.olur(d[1] + "\n");
if (s.equals("derlenmedi")) durdu++;
System.out.printf("%-40s %s%n", d[0], s);
}
System.out.println("derlemenin durdugu kurulum sayisi: " + durdu + " / " + DENEME.length);
}
}
kurulum sonuc tek soyut sinifi genisletme derlendi iki soyut sinifi genisletme derlenmedi soyut yontemi gerceklestirmeme derlenmedi iki arayuzu gerceklestirme derlendi iki arayuz ayni varsayilan, cozumsuz derlenmedi iki arayuz ayni varsayilan, sinif cozer derlendi arayuz ile soyut sinif ayni yontem derlendi alt arayuz ustteki varsayilani ezer derlendi derlemenin durdugu kurulum sayisi: 3 / 8
Sekiz kurulumun üçünde derleme duruyor, beşinde sürüyor. Duran üçü ayrı nedenlerle duruyor ve üçü de bu dersin iki aracını tanımlıyor.
İkinci satır soyut sınıfın kısıtıdır: iki sınıfı birden genişletmek yazılamıyor bile. Tek üstlü kalıtım bir tasarım önerisi değil, dilin reddettiği bir yazımdır. Üçüncü satır soyut sınıfın sözleşmesidir: gövdesiz bırakılan yöntem alt sınıfta doldurulmadığında sınıf somut olamaz. Soyut sınıf bir eksiklik bildirir ve eksikliğin kapatılmasını derleme zamanında zorlar.
Beşinci satır ise arayüzün karşılığıdır ve bu dersin çekirdek ölçümüdür. İki bağımsız arayüz aynı imzayla gövdeli birer yöntem verdiğinde derleyici karar vermeyi reddediyor. Ortada bir belirsizlik yoktur ki çözülsün — iki aday da eşit uzaklıktadır, ve dil aralarında bir öncelik tanımlamak yerine kaynağı geri çeviriyor. Kalıtımda bu durum hiç doğmaz; çoklu gerçekleştirmenin bedeli tam olarak budur.
Altıncı satır bedelin nasıl ödendiğini gösteriyor. Aynı iki arayüz, aynı çakışma — ama sınıf kendi gövdesini yazdığında kaynak derleniyor. Derleyicinin istediği şey adaylardan birini seçmek değil, seçimin kaynakta yazılmasıdır.
Derlenen satırların en sade ikisi, iki aracın neden yan yana durduğunu söylüyor. Birinci satır
soyut sınıfın verdiğidir: eksik bırakılan yöntem alt sınıfta doldurulunca sınıf somut olur ve
zincir kurulur. Dördüncü satır arayüzün verdiğidir: C sınıfı iki ayrı yeteneği aynı anda
üstleniyor ve ikisi de imzalarıyla çakışmadığı için ortada soru yoktur. Çakışma bir kural
değil, bir kesişme durumudur; yalnız iki bağımsız arayüz aynı imzayı verdiğinde doğar.
Ölçümün geri kalanı o kesişmenin nasıl kapatıldığını sayar.
Derleme Durmadığında Kim Kazanıyor
Kalan beş kurulumun üçünde ortada birden fazla aday vardır ve derleme yine de sürüyor. Bu durumlarda kararı kim veriyor?
- SN18 — Üç kurulumda da çağrı, bildirilen tipi
Tasiyiciolan bir referans üzerinden yapılır; değişen tek şey çalışma zamanı tipini kuran tip hiyerarşisidir. - SN19 — Her gövde kendi kaynağını metin olarak döndürür; kazanan taraf çıkarımla değil, dönen değerden okunur.
// Tasiyici.java — aynı yeteneği iki yoldan veren tipler
interface Tasiyici {
default String tasi() { return "arayuz varsayilani"; }
}
interface Etiketli extends Tasiyici {
@Override default String tasi() { return "alt arayuz varsayilani"; }
}
interface Ikinci {
default String tasi() { return "ikinci arayuz varsayilani"; }
}
abstract class SoyutTasiyici implements Tasiyici {
@Override public String tasi() { return "soyut sinif govdesi"; }
abstract String ad();
}
class Ozgul implements Etiketli { }
class SinifKazanir extends SoyutTasiyici implements Tasiyici {
@Override String ad() { return "sinif"; }
}
class Cozen implements Tasiyici, Ikinci {
@Override public String tasi() { return "sinif kendi cozdu"; }
}
class Secen implements Tasiyici, Ikinci {
@Override public String tasi() { return Ikinci.super.tasi(); }
}
// Kazanan.java — derleme durmadığında hangi taraf kazanıyor
public class Kazanan {
public static void main(String[] args) {
Tasiyici a = new Ozgul();
Tasiyici b = new SinifKazanir();
Tasiyici c = new Cozen();
Tasiyici e = new Secen();
System.out.printf("%-36s %s%n", "kaynak", "tasi() sonucu");
System.out.printf("%-36s %s%n", "arayuz, ustteki ve alttaki", a.tasi());
System.out.printf("%-36s %s%n", "arayuz varsayilani ve sinif govdesi", b.tasi());
System.out.printf("%-36s %s%n", "iki bagimsiz arayuz, sinif cozer", c.tasi());
System.out.printf("%-36s %s%n", "iki bagimsiz arayuz, sinif secer", e.tasi());
}
}
kaynak tasi() sonucu arayuz, ustteki ve alttaki alt arayuz varsayilani arayuz varsayilani ve sinif govdesi soyut sinif govdesi iki bagimsiz arayuz, sinif cozer sinif kendi cozdu iki bagimsiz arayuz, sinif secer ikinci arayuz varsayilani
Dört satır, dört ayrı seçim ve yalnız son ikisi kaynakta yazılıdır.
İlk satırda iki arayüz vardır, ama biri ötekini genişletiyor. Bu bir çakışma değildir:
Etiketli, Tasiyici’nin varsayılanını yeniden tanımlar ve daha özgül olan kazanır. Kural
kalıtımdaki kuralın aynısıdır; iki tip arasında bir alt–üst ilişkisi varsa alttaki seçilir.
İkinci satır dilin sessiz önceliğidir. SinifKazanir hem soyut sınıftan bir gövde, hem
arayüzden bir varsayılan alıyor. Kaynakta bir seçim yazılı değildir, buna karşın derleme
duruyor ve sonuç soyut sınıf gövdesi çıkıyor. Sınıf hattından gelen gövde arayüz
varsayılanını her zaman yener. Bu kural bir belirsizliği çözer, ama çözdüğünü kimseye
sormaz — arayüzü yazan taraf gövdesinin çağrılmadığını hiçbir yerde göremez.
Üçüncü satır ise beşinci kurulumun derlenen biçimidir. Cozen iki bağımsız arayüzü
gerçekleştiriyor ve kendi gövdesini yazıyor; kazanan kaynakta yazılan taraftır. Burada
çakışma çözülmemiş, yalnız aşılmıştır: iki varsayılan gövdenin ikisi de çağrılmıyor.
Dördüncü satır çakışmanın asıl çözümüdür. Secen de kendi gövdesini yazıyor, ama gövdesinde
adaylardan birini adıyla çağırıyor — sonuç ikinci arayuz varsayilani. Derleyicinin istediği
şey yeni bir davranış değildi; hangi adayın seçildiğinin yazılmasıydı, ve dil bu seçimi
yazmak için bir biçim veriyor. Reddedilen kaynak ile kabul edilen kaynak arasındaki fark bir
gerçekleştirim değil, bir imzadır.
Buradan dersin ana okuması çıkıyor. Derleyici üç ayrı davranış gösteriyor: bir hiyerarşi varsa en özgülü seçer, sınıf ile arayüz karşılaştığında sınıfı seçer, iki eşit arayüz karşılaştığında seçmez. Yalnız sonuncusu programcıya görünür. İlk ikisi de birer karardır ve ikisi de bildirilen tiplerin hiyerarşisine bakılarak derleme zamanında verilir; çalışma zamanı tipinin buradaki payı, hangi gövdenin çağrılacağını değil, o gövdeyi taşıyan sınıfın ne olduğunu belirlemekle sınırlıdır.
Sınırlayıcı Ölçüm: Durum Taşıyan Yetenek Arayüzle Kurulamaz
Tablo arayüzü çoklu gerçekleştirme yüzünden daha esnek gösteriyor. Sınır, arayüzün ne tutamadığındadır.
- SN20 — Üç kurulumun üçü de aynı yeteneği kurar: bir sayacı bir artıran tek bir işlem. Değişen tek şey sayacın nerede tutulduğudur.
// Durum.java — durum taşıyan bir yetenek hangi araçla kurulabiliyor
public class Durum {
static final String[][] DENEME = {
{ "arayuzde sabit bildirimi",
"interface Sayac { int BASLANGIC = 0; }" },
{ "arayuzde ornek alani ve artirma",
"interface Sayac { int deger = 0; default void artir() { deger++; } }" },
{ "soyut sinifta alan ve artirma",
"abstract class Sayac { int deger = 0; void artir() { deger++; } }" },
};
public static void main(String[] args) throws Exception {
System.out.printf("%-36s %s%n", "yetenek nasil kuruluyor", "sonuc");
for (String[] d : DENEME)
System.out.printf("%-36s %s%n", d[0], Derle.olur(d[1] + "\n"));
System.out.println();
Adet x = new Adet(), y = new Adet();
x.artir(); x.artir(); y.artir();
System.out.println("soyut siniftan turetilen iki ornek : " + x.deger + " ve " + y.deger);
System.out.println("arayuzdeki sabit ornek olmadan okunur: " + Sabitli.BASLANGIC);
}
}
abstract class SoyutSayac {
int deger = 0;
void artir() { deger++; }
}
class Adet extends SoyutSayac { }
interface Sabitli { int BASLANGIC = 0; }
yetenek nasil kuruluyor sonuc arayuzde sabit bildirimi derlendi arayuzde ornek alani ve artirma derlenmedi soyut sinifta alan ve artirma derlendi soyut siniftan turetilen iki ornek : 2 ve 1 arayuzdeki sabit ornek olmadan okunur: 0
İlk satır yanıltıcıdır: arayüzde bir alan bildirilebiliyor. İkinci satır onun ne olduğunu söylüyor — o alan artırılamaz, çünkü örtük olarak sabittir. Arayüzde yazılan her alan bildirimi, yazılmasa da, sınıf düzeyinde ve değiştirilemezdir. Son satırın gösterdiği gibi hiçbir örnek kurulmadan okunabiliyor, yani bir örneğe ait değildir.
Üçüncü satır ile alttaki koşum karşı tarafı ölçüyor. Aynı yetenek soyut sınıfla kurulduğunda derleniyor ve iki örnek 2 ile 1 sayıyor: her nesnenin kendi sayacı var. Yeteneğin gerektirdiği şey bir gövde değil, gövdenin üzerinde çalışacağı örneğe ait bir yerdi, ve arayüz o yeri veremiyor. İki sayının ayrı çıkması ölçümün kendisidir: tek bir yer paylaşılsaydı her iki referans da 3 okurdu.
Sınırın anlamı, dersin ilk yarısındaki esnekliği tersine çeviriyor. Arayüz çoklu takılabilir olduğu için daha geniş görünür; oysa taşıyabildiği şey yalnız davranıştır. Bir yetenek durum tutuyorsa çoklu gerçekleştirme zaten sorulamaz, çünkü tek aday vardır. İki aracın seçimi bir üslup sorusu değildir: yetenek durumsuzsa iki yol da açıktır ve arayüz çoklu takılmayı kazandırır; yetenek durum tutuyorsa yol tektir ve onunla birlikte tek üstlü kalıtımın kısıtı da gelir.
Özet
- Aynı yeteneğin sekiz kurulumundan üçünde derleme durur: iki sınıfı birden genişletmek, soyut yöntemi doldurmamak, ve iki bağımsız arayüzün aynı varsayılanı vermesi.
- İki bağımsız arayüz aynı imzayla gövde verdiğinde derleyici karar vermeyi reddeder; aynı kaynak, sınıf kendi gövdesini yazdığında derlenir. İstenen şey yeni bir davranış değil, seçimin kaynakta yazılmasıdır — sınıf adaylardan birini adıyla da çağırabilir.
- Derleme durmadığında karar yine derleyicinindir ve sessizdir: hiyerarşi varsa en özgül arayüz kazanır, sınıf ile arayüz karşılaştığında sınıf gövdesi kazanır.
- Sessiz kararın bedeli görünmezliğidir: arayüz varsayılanını yazan taraf, gövdesinin hiç çağrılmadığını kendi kaynağına bakarak göremez.
- Arayüzde bildirilen alan örtük olarak sabittir ve örnek olmadan okunur; artırılmaya çalışıldığında kaynak derlenmez.
- Aynı sayaç soyut sınıfla kurulduğunda iki örnek 2 ile 1 sayar: durum taşıyan bir yetenek arayüzle kurulamaz, ve o yolu seçmek tek üstlü kalıtımın kısıtını da getirir.
Sonraki Adım
Bu derste iki soyutlama aracı da örnek düzeyinde çalışıyordu: soyut sınıfın gövdesi de, arayüzün varsayılan yöntemi de bir nesne üzerinden çağrıldı ve hangi gövdenin yürütüleceği sorusu hep bir örneğin çalışma zamanı tipiyle birlikte soruldu. Sıradaki ders sınıfın kendisine ait üyelere bakar ve kararın tarafının orada ters döndüğünü ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.