Soyut veri tipleri (ADT) ve kapsülleme
Soyutlama kavramı, ADT'nin iki koşulu, information hiding, Ada paketleri, C++ ve Java sınıfları, parametreli ADT'ler (generics) ve büyük programlar için kapsülleme yapıları (namespace, package).
Bir programın karmaşıklığıyla başa çıkmanın yolu soyutlama (abstraction): bir varlığın sadece o an önemli olan özelliklerini görmek, gerisini gizlemek. Alt programlar process abstraction’dır (sıralama algoritmasını bilmeden sort çağırırsın). Bu hafta ikincisi: data abstraction, yani abstract data type (ADT). Sınavda kavramsal sorular (ADT’nin iki koşulu, information hiding’in faydası, dil karşılaştırmaları) ve bir ADT’yi kullanan kodun çıktısı sorulur.
Altın kurallar:
- ADT = gizli temsil + tek birimde tanımlanmış tip ve işlemler.
- Kullanıcı sadece arayüzü görür. İçeriyi değiştirmek kullanıcı kodunu bozmaz.
- Yığın LIFO, kuyruk FIFO. Çıktı sorularında içeriği her adımda yaz.
- Parametreli ADT tip hatalarını derlemede yakalar.
Abstract data type
Kullanıcı tanımlı bir tip, iki koşulu sağlıyorsa ADT’dir:
- Temsil gizli: tipin nesnelerinin iç yapısı, tipi kullanan birimlerden gizlidir. Kullanıcı nesnelere sadece tipin tanımladığı işlemlerle erişir.
- Tek birim: tipin bildirimi ve üzerindeki işlemlerin protokolleri tek bir sözdizimsel birimde yer alır. Diğer birimler bu tipten değişken yaratabilir.
Aslında float da bir ADT gibi: bitlerin nasıl dizildiğini bilmeden + ve * kullanırsın. Kullanıcı tanımlı ADT’ler aynı rahatlığı programcının tiplerine getirir.
Information hiding’in faydaları:
- Reliability: kullanıcı nesnenin iç durumunu bozamaz (yığının
topalanını rastgele bir değere çekemez). - Değiştirilebilirlik: temsil değişirse (dizi yerine bağlı liste) kullanıcı kodu değişmez, çünkü sadece arayüze bağlı.
- Daha az isim çakışması ve daha küçük, yönetilebilir birimler.
Java’da ADT: sınıf
static class IntStack {
private int[] items = new int[10];
private int top = -1;
public void push(int x) { items[++top] = x; }
public int pop() { return items[top--]; }
public int peek() { return items[top]; }
public boolean isEmpty() { return top == -1; }
}
IntStack s = new IntStack();
s.push(3); s.push(7); s.pop(); s.push(2); s.push(9);
System.out.println(s.pop() + s.peek());
11
işlem yığın (alt → üst)
push 3 [3]
push 7 [3, 7]
pop [3] (7 döndü)
push 2 [3, 2]
push 9 [3, 2, 9]
pop [3, 2] (9 döndü)
peek (2, silinmez) → 9 + 2 = 11
Dışarıdan s.top = 5; ya da a.balance = 5000; yazmak derleme hatası verir:
error: balance has private access in Account
Java’da erişim düzeyleri: private (sadece sınıf), paket erişimi (belirtilmezse: aynı paket), protected (paket + alt sınıflar), public. Erişim sınıf düzeyindedir: bir Money nesnesi başka bir Money nesnesinin private alanını okuyabilir.
Immutable ADT
static final class Money {
private final int cents;
Money(int c) { cents = c; }
Money add(Money o) { return new Money(cents + o.cents); }
int cents() { return cents; }
}
Money m1 = new Money(150);
Money m2 = m1.add(new Money(75));
System.out.println(m1.cents() + " " + m2.cents());
150 225
Değişmeyen nesneler paylaşılabilir; aliasing sorunları (bir yerden değiştirilip başka yerden şaşırma) ortadan kalkar. Java’nın String’i de böyle.
Arayüz ile temsil ayrı
List<Integer> nums = new ArrayList<>(List.of(4, 1, 3));
List<Integer> view = Collections.unmodifiableList(nums);
try { view.add(5); } catch (UnsupportedOperationException e) { System.out.println("UnsupportedOperationException"); }
nums.add(5);
System.out.println(view.size());
UnsupportedOperationException
4
Diğer dillerde ADT
| Dil | Yapı | Gizleme | Not |
|---|---|---|---|
| Ada | package: specification + body | spec’in private kısmı | private tip: :=, = var; limited private: yok |
| C++ | class (ve struct) | private, protected, public | class varsayılan private, struct public; friend erişim verir; constructor/destructor |
| Java | class | erişim belirleyicileri | Tüm nesneler heap’te, destructor yok (garbage collection) |
| C# | class, struct | Java’ya ek olarak internal | property: get/set ile alan gibi görünen accessor |
| Ruby | class | metotlar varsayılan public, alanlar her zaman gizli | attr_reader accessor üretir |
Ada’da specification package’ın private kısmı derleyicinin görmesi için oradadır (nesnenin boyutunu bilmesi gerekir) ama kullanıcı oradaki alanlara erişemez. Temsili tamamen gizlemek için tip bir pointer olarak bildirilir, gösterdiği yapı body’de tanımlanır.
C++‘ta destructor nesnenin ömrü bitince otomatik çağrılır ve heap’te ayırdığı belleği geri verir. Yaratılışın tersi sırasıyla çalışırlar. Java’da benzer bir sıra try-with-resources’ta görülür:
try (Res r1 = new Res("A"); Res r2 = new Res("B")) {
System.out.print("body ");
}
open A open B body close B close A
Parametreli ADT’ler
Eleman tipi ne olursa olsun aynı işi yapan bir yığın yazmak istiyorsun. Parameterized ADT (generic class) tip parametresi alır:
| Dil | Sözdizimi |
|---|---|
| Ada | generic type Element is private; package Stack is ... |
| C++ | template <class T> class Stack { ... }; |
| Java 5+ | class Stack<T> { ... } (sadece referans tipleri; int değil Integer) |
| C# 2005+ | class Stack<T> |
List<Integer> xs = new ArrayList<>();
xs.add("five");
error: no suitable method found for add(String)
C++ template’leri her farklı tip için ayrı kod üretir (derlemede örnekleme). Java generics tek bir kod kullanır ve tip bilgisi derlemeden sonra silinir (type erasure); bu yüzden new T() yazılamaz.
Büyük programlar için kapsülleme
ADT tek bir tipi kapsüller. Binlerce sınıflı bir sistemde ilgili tipleri ve alt programları gruplamak, ayrı derlemek ve isim çakışmalarını önlemek gerekir.
- C: başlık dosyası (
.h, arayüz) + uygulama dosyası (.c). Derleyici iki dosyanın tutarlılığını garanti etmez. - C++, C#:
namespace.std::coutgibi nitelikli isimler ya dausing. - Java:
package. Paket hem isim alanı hem erişim birimidir: belirleyicisi olmayan üyeler paket içinde görünür. - Ada: package’lar iç içe ve ayrı derlenebilir.
- Ruby:
module.
Sık yapılan hatalar
| Yanlış | Doğrusu |
|---|---|
| ADT = herhangi bir struct | Temsil gizli olmalı |
| Gizleme sadece güvenlik için | Asıl kazanç: temsil değişince kullanıcı kodu değişmez |
| peek() elemanı siler | Sadece okur |
| Kaynaklar açılış sırasıyla kapanır | Ters sırayla |
| C++ class varsayılan public | private; struct public |
| Java’da private = nesneye özel | Sınıfa özel; aynı sınıfın başka nesnesi erişir |
| Ada private tipte atama yok | Var; limited private’ta yok |
List<int> Java’da geçerli | List<Integer>; generic parametre referans tipi |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
State the two conditions that a user-defined type must satisfy to be an abstract data type.
Cevabı göster
(1) Information hiding: tipin nesnelerinin temsili (iç yapısı) onu kullanan birimlerden gizlidir, kullanıcı sadece tanımlı işlemlerle erişir. (2) Tipin bildirimi ve işlemlerinin protokolleri tek bir sözdizimsel birimde (class, package) toplanır; diğer birimler bu tipten değişken yaratabilir.
What is printed in Java?
IntStack s = new IntStack(); // push, pop, peek on an int array
s.push(3); s.push(7); s.pop(); s.push(2); s.push(9);
System.out.println(s.pop() + s.peek());Cevabı göster
Yığın LIFO: [3] → [3, 7] → pop 7 → [3] → [3, 2] → [3, 2, 9]. pop() 9 döner, yığın [3, 2]. peek() 2 (silmez). 9 + 2 = 11.
What is printed in Java?
IntStack t = new IntStack();
for (int i = 1; i <= 4; i++) t.push(i * i);
int sum = 0;
while (!t.isEmpty()) { int v = t.pop(); if (v % 2 == 0) sum += v; }
System.out.println(sum);Cevabı göster
Yığına 1, 4, 9, 16 girer. Ters sırayla çıkar: 16, 9, 4, 1. Çiftler 16 + 4 = 20. Kullanıcı kod içerideki diziyi hiç görmedi; sadece push, pop, isEmpty kullandı.
What is printed? Queue2<T> wraps a LinkedList with enqueue (add at end), dequeue (remove from front) and size.
Queue2<String> q = new Queue2<>();
q.enqueue("a"); q.enqueue("b"); q.dequeue(); q.enqueue("c");
System.out.println(q.dequeue() + q.size());Cevabı göster
Kuyruk FIFO: [a, b] → dequeue a → [b] → [b, c] → dequeue b, kalan [c]: size 1. Sonuç "b" + 1 = "b1". Queue2 parametreli (generic) bir ADT: aynı kod her eleman tipiyle çalışır.
Does this Java code compile? Why?
class Account {
private int balance = 100;
public int getBalance() { return balance; }
}
// in another class:
Account a = new Account();
a.balance = 5000;Cevabı göster
private üyeye sadece sınıfın içinden erişilir. Dışarıdan doğrudan değiştirme engellenir: information hiding. Hesap bakiyesinin geçerli kalması (negatif olmaması gibi) sınıfın kendi metotlarıyla garanti edilebilir. Okumak için accessor (getBalance) kullanılır.
In what order are the messages printed?
// Res(String n) prints "open n", close() prints "close n"
try (Res r1 = new Res("A"); Res r2 = new Res("B")) {
System.out.print("body ");
}Cevabı göster
Kaynaklar yazıldığı sırayla açılır, blok bitince ters sırayla kapanır. Bu, C++'ta destructor'ların yapıcıların tersi sırasıyla çağrılmasına benzer: sonra yaratılan, önce yaratılana bağlı olabilir, o yüzden önce o temizlenir.
Does this compile? Which ADT feature does it illustrate?
List<Integer> xs = new ArrayList<>();
xs.add("five");Cevabı göster
List<Integer> parametreli bir ADT'nin Integer ile örneklenmiş halidir. String eklemek derleme zamanında yakalanır: "no suitable method found for add(String)". Java 5'ten önceki ham koleksiyonlar Object tutardı ve hata ancak çalışırken, cast sırasında çıkardı.
In C++, what is the default access of members of a class and of a struct?
Cevabı göster
C++'ta class ile struct arasındaki tek fark varsayılan erişim. Üç erişim düzeyi: private (sadece sınıf ve friend'ler), protected (ayrıca alt sınıflar), public (herkes). Java'da belirtilmezse package erişimi (aynı paketteki sınıflar) geçerli.
In Ada, what is the difference between a private type and a limited private type declared in a package specification?
Cevabı göster
Ada'da ADT bir package ile tanımlanır: specification package arayüzü, body package uygulamayı tutar. private tipte kullanıcı :=, = ve /= kullanabilir ama iç yapıya erişemez. limited private'ta bunlar da yoktur; sadece paketin verdiği işlemler kullanılabilir. Pointer içeren tipler için kopyalamanın anlamını paket belirlemeli.
What is printed in Java?
static final class Money {
private final int cents;
Money(int c) { cents = c; }
Money add(Money o) { return new Money(cents + o.cents); }
int cents() { return cents; }
}
// in main:
Money m1 = new Money(150);
Money m2 = m1.add(new Money(75));
System.out.println(m1.cents() + " " + m2.cents());Cevabı göster
Money immutable bir ADT: alan final ve private, add yeni bir nesne döndürür, m1'i değiştirmez. Dikkat: o.cents başka bir Money nesnesinin private alanı ama aynı sınıfın içinden erişildiği için geçerli. Java'da erişim sınıf düzeyindedir, nesne düzeyinde değil.
What is printed in Java?
List<Integer> nums = new ArrayList<>(List.of(4, 1, 3));
List<Integer> view = Collections.unmodifiableList(nums);
try { view.add(5); }
catch (UnsupportedOperationException e) { System.out.println("UnsupportedOperationException"); }
nums.add(5);
System.out.println(view.size());Cevabı göster
view, aynı listeye sadece okuma işlemleri sunan bir arayüzdür; değiştirme işlemleri istisna fırlatır. Ama veri kopyalanmadı: nums değişince view da değişir (4 eleman). Arayüz ile temsilin ayrılması tam olarak bu.
Name the encapsulation construct for large programs: (a) in C++ and C#; (b) in Java; (c) in Ada.
Cevabı göster
Büyük programlarda isim çakışmalarını önlemek ve ilgili kodu birlikte derlemek için ADT'den büyük kapsüller gerekir. C++ namespace ve using, C# namespace, Java package ve import (paket erişimi de verir), Ada package'ları (iç içe olabilir). C'de bu iş başlık dosyaları (.h) ve ayrı derlenen .c dosyalarıyla yapılır.
Doğrulama: Yığın/kuyruk ADT çıktıları, try-with-resources kapanış sırası, immutable sınıf ve unmodifiable view örnekleri JDK ile çalıştırıldı (java verify/SEN2001/W10.java). private erişim ve generic tip hatası örnekleri javac ile derlenmeye çalışıldı ve beklenen derleme hatalarını verdi (verify/SEN2001/w10b/W10b.java, W10c.java). C++ ve Ada örnekleri kavramsal; derleyicileri kurulu değil.