---
title: 'Veri Erişim Soyutlaması'
source: 'https://academia.sh/tr/kurslar/kurumsal-uygulama-cercevesi/veri-erisim-soyutlamasi'
course: 'Kurumsal Uygulama Çerçevesi'
language: tr
updated: '2026-08-23T14:25:05+00:00'
license: 'CC BY-SA 4.0'
---

# Veri Erişim Soyutlaması

Aynı derlenmiş çağıran kümesi — nesne kabı, servis ve giriş noktası — bir depo arayüzünün iki ayrı gerçekleştirimiyle, hiç yeniden derlenmeden koşturuluyor. Çağıranın sınıf dosyası yalnız arayüzü anıyor; aynı çağrı bir gerçekleştirimde sorunsuz çalışırken ötekinde bir istisnayla düşüyor, çünkü arayüz sözü ile gerçekleştirim davranışı ayrı ve kap bu ayrımı kapatmıyor, yalnız gizliyor.

Önceki ders bir isteğin verisinin doğru olup olmadığını ölçtü, ama o veri işlendikten sonra
nereye yazıldığı hiç sorulmadı. Bu ders bunu ele alıyor: bir **depo** arayüzünün
gerçekleştirimini kabın nasıl sağladığını, ve aynı çağıranın yeniden derlenmeden iki ayrı
gerçekleştirimle nasıl koşabildiğini ölçüyor. Depo kavramı, bir iş mantığının kalıcılık
ayrıntılarından bağımsız kalması **M16/K05 Veri Erişim Katmanı** kursunda kuruldu ve burada
tekrarlanmıyor; bu dersin sorusu ondan ayrı — kabın bu arayüzü hangi düzenekle doldurduğu.

Veritabanı burada da **modellenir**: gerçek bir bağlantı açılmaz, gerçek bir SQL ifadesi
çalıştırılmaz. `Depo` yalnız iki yöntem taşıyan bir arayüz — bir miktarı okumak, bir miktarı
yazmak — ve bu iki yöntemin arkasında ne durduğu çağıran için görünmez. Görünmezliğin ne
kadar gerçek olduğunu göstermenin tek yolu, aynı çağıranı gerçekten iki ayrı gerçekleştirimle
koşturmaktır; bu derste kullanılan yöntem tam olarak bu.

Önceki derslerde "aynı davranışı iki yolla koşturmak" kabın kurduğu nesneyle elle kurulan
nesneyi karşılaştırmak anlamına geliyordu. Bu ders aynı ilkeyi bir adım öteye taşıyor: burada
karşılaştırılan iki nesne değil, aynı çağıran kodun iki ayrı **gerçekleştirime** karşı
koşturulmasıdır. Bir arayüzün asıl sınandığı yer de burası — bir sınıfın davranışını taklit
etmek yetmiyor, aynı sözleşmeyi tutan tamamen farklı bir iç yapının da aynı çağıran kodu
memnun edip etmediği görülmesi gerekiyor.

## Aynı Çağıran, İki Ayrı Gerçekleştirim

- **WV16.** Bu ölçüm gerçek bir sınıf yolu ayrımını sahneye koyar: arayüz, çağıran kod ve
  her gerçekleştirim ayrı dizinlere derlenir, sonra çalışma zamanında farklı kombinasyonlarla
  bir araya getirilir. Bir sınıfın hangi sınıf yolunda gerekmek zorunda olduğu **M08/K05
  Derleme, Test ve Ekosistem** kursunda ölçüldü; burada aynı düzenek arayüz–gerçekleştirim
  ayrımı için kullanılıyor, sayılar oradan tekrarlanmıyor.
- **WV17.** `Kap.kur` yöntemi gerçekleştirim sınıfının adını bir dizge parametresi olarak
  alıyor ve `Class.forName` ile buluyor; bu, çağıranın hiçbir gerçekleştirim sınıfını
  derleme zamanında anmadan bir örnek kurabilmesinin yolu.

`Kap`, `Servis` ve `Ana` sınıfları yalnız `Depo` arayüzüne karşı derleniyor — derleme sınıf
yoluna `BellekDepo` da `DiziDepo` da hiç girmiyor. Bu üç sınıf tek bir kez derleniyor, sonra
iki ayrı çalışma zamanı sınıf yoluyla, hiç yeniden derlenmeden koşturuluyor: birinde
gerçekleştirim `BellekDepo`, ötekinde `DiziDepo`. Aradaki fark yalnız `kos` çağrısına verilen
üçüncü sınıf yolu bileşeni ve `Ana.main`'e geçirilen gerçekleştirim adı.

```java
// IkiGercek.java — ayni derlenmis cagiran, arayuzu degistirmeden iki ayri gerceklestirimle kosuyor
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class IkiGercek {

    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static void derle(Path cikti, List<Path> sinifYolu, List<Path> kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter cikisYazi = new StringWriter();
        int sonuc = derleyici.run(new PrintWriter(cikisYazi), new PrintWriter(cikisYazi), arg.toArray(new String[0]));
        if (sonuc != 0) throw new RuntimeException("derleme basarisiz: " + cikisYazi);
    }

    static String kos(List<Path> sinifYolu, String gerceklestirimAdi) {
        URL[] url = new URL[sinifYolu.size()];
        try {
            for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
            try (URLClassLoader yukleyici = new URLClassLoader(url, IkiGercek.class.getClassLoader().getPlatformClassLoader())) {
                Class<?> ana = Class.forName("Ana", true, yukleyici);
                ByteArrayOutputStream tampon = new ByteArrayOutputStream();
                PrintStream eski = System.out;
                System.setOut(new PrintStream(tampon));
                try {
                    ana.getMethod("main", String[].class).invoke(null, (Object) new String[]{gerceklestirimAdi});
                } finally {
                    System.setOut(eski);
                }
                return tampon.toString().strip();
            }
        } catch (InvocationTargetException e) {
            return "istisna: " + e.getCause().getClass().getSimpleName() + ": " + e.getCause().getMessage();
        } catch (Throwable t) {
            return "istisna: " + t.getClass().getSimpleName();
        }
    }

    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("ikigercek");
        Path kaynak = kok.resolve("kaynak");

        Path sArayuz = yaz(kaynak.resolve("arayuz"), "Depo",
                "public interface Depo {\n"
              + "  int miktarBul(String urun);\n"
              + "  void miktarGuncelle(String urun, int miktar);\n"
              + "}\n");

        Path sKap = yaz(kaynak.resolve("cagiran"), "Kap",
                "public class Kap {\n"
              + "  static Servis kur(String gerceklestirimAdi) throws ReflectiveOperationException {\n"
              + "    Class<?> c = Class.forName(gerceklestirimAdi);\n"
              + "    Depo depo = (Depo) c.getDeclaredConstructor().newInstance();\n"
              + "    return new Servis(depo);\n"
              + "  }\n"
              + "}\n");
        Path sServis = yaz(kaynak.resolve("cagiran"), "Servis",
                "public class Servis {\n"
              + "  private final Depo depo;\n"
              + "  public Servis(Depo depo) { this.depo = depo; }\n"
              + "  public String uygula(String urun, int degisim) {\n"
              + "    int mevcut = depo.miktarBul(urun);\n"
              + "    int yeni = mevcut + degisim;\n"
              + "    depo.miktarGuncelle(urun, yeni);\n"
              + "    return urun + \": \" + mevcut + \" -> \" + yeni;\n"
              + "  }\n"
              + "}\n");
        Path sAna = yaz(kaynak.resolve("cagiran"), "Ana",
                "public class Ana {\n"
              + "  public static void main(String[] args) throws Exception {\n"
              + "    Servis servis = Kap.kur(args[0]);\n"
              + "    System.out.println(servis.uygula(\"vida\", 5));\n"
              + "    System.out.println(servis.uygula(\"vida\", -2));\n"
              + "    System.out.println(servis.uygula(\"civi\", 3));\n"
              + "  }\n"
              + "}\n");

        Path sBellek = yaz(kaynak.resolve("bellek"), "BellekDepo",
                "import java.util.*;\n"
              + "public class BellekDepo implements Depo {\n"
              + "  private final Map<String, Integer> veri = new HashMap<>();\n"
              + "  public int miktarBul(String urun) { return veri.getOrDefault(urun, 0); }\n"
              + "  public void miktarGuncelle(String urun, int miktar) { veri.put(urun, miktar); }\n"
              + "}\n");
        Path sDizi = yaz(kaynak.resolve("dizi"), "DiziDepo",
                "public class DiziDepo implements Depo {\n"
              + "  private final String[] adlar = new String[1];\n"
              + "  private final int[] miktarlar = new int[1];\n"
              + "  private int dolu = 0;\n"
              + "  public int miktarBul(String urun) {\n"
              + "    for (int i = 0; i < dolu; i++) if (adlar[i].equals(urun)) return miktarlar[i];\n"
              + "    return 0;\n"
              + "  }\n"
              + "  public void miktarGuncelle(String urun, int miktar) {\n"
              + "    for (int i = 0; i < dolu; i++) if (adlar[i].equals(urun)) { miktarlar[i] = miktar; return; }\n"
              + "    if (dolu == adlar.length) throw new IllegalStateException(\"depo kapasitesi dolu: \" + urun + \" eklenemedi\");\n"
              + "    adlar[dolu] = urun; miktarlar[dolu] = miktar; dolu++;\n"
              + "  }\n"
              + "}\n");

        Path cArayuz = kok.resolve("cikti/arayuz");
        Path cCagiran = kok.resolve("cikti/cagiran");
        Path cBellek = kok.resolve("cikti/bellek");
        Path cDizi = kok.resolve("cikti/dizi");

        derle(cArayuz, List.of(), List.of(sArayuz));
        derle(cCagiran, List.of(cArayuz), List.of(sKap, sServis, sAna));
        derle(cBellek, List.of(cArayuz), List.of(sBellek));
        derle(cDizi, List.of(cArayuz), List.of(sDizi));

        System.out.println("-- ayni cagiran (Kap+Servis+Ana), BellekDepo gerceklestirimiyle --");
        System.out.println(kos(List.of(cCagiran, cArayuz, cBellek), "BellekDepo"));

        System.out.println("-- ayni cagiran, hic yeniden derlenmeden, DiziDepo gerceklestirimiyle --");
        System.out.println(kos(List.of(cCagiran, cArayuz, cDizi), "DiziDepo"));
        System.out.println("kusur turu: hata (istisna adiyla dustu, DiziDepo kapasitesini asan cagriyi reddetti)");
    }
}
```

```
-- ayni cagiran (Kap+Servis+Ana), BellekDepo gerceklestirimiyle --
vida: 0 -> 5
vida: 5 -> 3
civi: 0 -> 3
-- ayni cagiran, hic yeniden derlenmeden, DiziDepo gerceklestirimiyle --
istisna: IllegalStateException: depo kapasitesi dolu: civi eklenemedi
kusur turu: hata (istisna adiyla dustu, DiziDepo kapasitesini asan cagriyi reddetti)
```

`Servis` sınıfının kaynağında `BellekDepo` ya da `DiziDepo` adı hiçbir yerde geçmiyor —
yalnız `Depo depo` alanı var, ve o alanın türü arayüz. `Kap.kur` da aynı şekilde: aldığı tek
bilgi bir dizge, `Class.forName` ile bulduğu sınıfı `Depo`'ya döküyor. Bu üç sınıf ilk
`derle` çağrısında, hiçbir gerçekleştirimin sınıf yolunda bulunmadığı bir ortamda derleniyor,
ve derlenmiş hâlleri iki ayrı `kos` çağrısında tekrar kullanılıyor. Aradaki fark yalnız
`kos`'a verilen sınıf yolu listesinin üçüncü bileşeni.

Bu derlemenin gerçekten "yeniden derlenmeden" olduğunu doğrulayan şey, `derle(cCagiran, ...)`
çağrısının yalnız bir kez yapılması. `cCagiran` dizini bir kez üretiliyor ve iki `kos`
çağrısına da aynen veriliyor; ikinci `kos` çağrısı öncesinde `Kap`, `Servis` ya da `Ana`
kaynağına dönüp tekrar `derle` çağıran hiçbir satır yok. Gerçek bir kurulumda bu, bir
uygulamanın dağıtım paketinin (çağıran sınıfları içeren kısmı) bir kez üretilip, hangi
gerçekleştirimin bağlanacağının yalnız çalışma zamanı sınıf yoluyla belirlendiği anlamına
gelir — kaynak koddaki hiçbir değişiklik gerekmeden, hiçbir yeniden derleme adımı
çalıştırmadan, aynı `.class` dosyaları farklı bir depoya bağlanabiliyor.

## Arayüz Sözü ile Gerçekleştirim Davranışı Ayrıdır

- **WV18.** `BellekDepo` bir `HashMap` üzerinde çalışıyor ve kaç farklı `urun` adı
  geldiğine bir sınır koymuyor. `DiziDepo` sabit uzunlukta iki dizi kullanıyor ve yalnız
  bir farklı `urun` adını tutabiliyor.
- **WV19.** `Depo` arayüzü bu kapasite farkını hiçbir yerde belirtmiyor; iki yöntemin
  imzası da bir sınır sözü taşımıyor.

İlk koşumda `vida` iki kez güncelleniyor (`0 -> 5`, sonra `5 -> 3`), ardından farklı bir
üründe (`civi`) bir kayıt daha açılıyor (`0 -> 3`). `BellekDepo` ile bu üç çağrı da sorunsuz
tamamlanıyor. `DiziDepo` ile aynı üç çağrı, aynı sırayla, aynı derlenmiş `Servis` sınıfı
üzerinden yapılıyor — ama üçüncüsü bir istisnayla düşüyor: `DiziDepo` yalnız bir farklı ürün
adını tutabiliyor, ve `civi` ikinci bir ürün adı olarak geldiğinde kapasite doluyor.

Bu, dersin sınırlayıcı ölçümü: **arayüz sözü ile gerçekleştirim davranışı ayrıdır, ve kap bu
ayrımı kapatmaz, yalnız gizler.** `Depo` arayüzü `miktarGuncelle`'nin her zaman başarılı
olacağını hiçbir yerde vaat etmiyor, ama pratikte `BellekDepo` ile çalışırken bu vaat hiç
test edilmeden doğru görünüyor — sınır hiç aşılmadığı için sorun hiç ortaya çıkmıyor. Aynı
kod `DiziDepo`'ya bağlandığında, arayüzün hiç söz vermediği bir kapasite sınırı aniden
görünür oluyor. Kap, `Servis`'in hangi gerçekleştirimle çalıştığını çağırandan gizliyor, ama
gerçekleştirimlerin kendi aralarındaki davranış farkını **ortadan kaldırmıyor** — yalnız
o farkın nerede ortaya çıkacağını, arayüzün arkasına, çalışma zamanına erteliyor.

Bu ayrımın bir de tersi var: iki gerçekleştirim de `vida` ürünü üzerindeki iki çağrıda
birebir aynı sonucu veriyor. Arayüzün gerçekten sağladığı şey bu — tek bir anahtarla yapılan
oku-yaz döngüsü, gerçekleştirimden bağımsız olarak aynı davranıyor. Sorun yalnız arayüzün
**söylemediği** bir varsayımın (sınırsız sayıda farklı anahtar) bir gerçekleştirimde geçerli,
ötekinde geçersiz olmasından doğuyor. Bir arayüz, üzerinde imzası bulunan yöntemlerin sözünü
verir; imzada yer almayan her şey — kapasite, eşzamanlılık, gecikme — gerçekleştirimin kendi
kararıdır ve arayüz onu hiç taşımaz.

Bu, kabın veri erişimi katmanındaki asıl katkısını da açıklığa kavuşturuyor. Kap, `Servis`
sınıfını hangi gerçekleştirimin dolduracağına karar veriyor, ama bu kararın **doğru**
gerçekleştirimi seçmiş olduğunu hiçbir yerde garanti etmiyor — `Kap.kur`, kendisine verilen
dizge adının gerçekten `Depo`'yu uygulayan bir sınıfı gösterdiğini bile yalnız çalışma
zamanında, `(Depo) c.getDeclaredConstructor().newInstance()` satırındaki döküm başarısız
olmadığı sürece anlıyor. Bir gerçekleştirimin arayüzün taşımadığı bir varsayımı ihlal etmesi
de aynı şekilde yalnız o varsayım gerçekten test edildiğinde ortaya çıkıyor — `DiziDepo` ile
tek bir ürün adı kullanan bir çağıran hiçbir zaman kapasite sınırını görmez. Bu derste
üçüncü çağrının farklı bir ürün adı taşıması bilinçli bir seçim: sınır yalnız iki farklı
anahtar aynı depoya ulaştığında görünür oluyor, ve bu da arayüz testinin neden yalnız
"yöntem çağrılıyor mu" değil, "gerçekleştirimin gerçek sınırları nerede" sorusunu da
sorması gerektiğini gösteriyor.

## Özet

- `Kap`, `Servis` ve `Ana` sınıfları yalnız `Depo` arayüzüne karşı bir kez derlendi ve hiç
  yeniden derlenmeden iki ayrı gerçekleştirimle (`BellekDepo`, `DiziDepo`) koşturuldu.
- Çağıranın sınıf dosyasında hiçbir gerçekleştirim sınıfının adı geçmiyor; `Kap.kur`
  gerçekleştirimi yalnız bir dizge adıyla, yansımayla buluyor.
- Aynı üç çağrı `BellekDepo` ile sorunsuz tamamlandı, `DiziDepo` ile üçüncü çağrıda bir
  `IllegalStateException` ile düştü — aynı derlenmiş kod, aynı sıradaki çağrılar, iki ayrı
  sonuç.
- `Depo` arayüzü kapasite gibi imzada yer almayan hiçbir garantiyi taşımıyor; bu tür
  farklar yalnız gerçekleştirim değiştiğinde ortaya çıkıyor.
- Kap, çağırandan gerçekleştirim seçimini gizliyor ama gerçekleştirimler arasındaki
  davranış farkını ortadan kaldırmıyor; farkı yalnız çalışma zamanına erteliyor.

## Sonraki Adım

Bu ders bir arayüzün arkasında hangi gerçekleştirimin durduğunu ölçtü, ama gerçekleştirimin
kendi alanlarının nesne ile depo arasında nasıl eşleştiği hiç sorulmadı. Sıradaki ders bunu
ele alıyor: bir nesnenin alanları ile bir tablonun sütunları arasındaki bağlamanın kaynakta
bir açıklamadan geldiğini, ve erişilmeyen bir alanın hiç yüklenmediğini ölçüyor.
