Abstract Sınıflar ve Composition
Abstract Sınıf Nedir?
Abstract sınıf, doğrudan nesne oluşturulamayan ama başka sınıflar tarafından extend edilebilen sınıftır. Hem tamamlanmış metotlar hem de gövdesiz (abstract) metotlar içerebilir.
Bunu bir mimari plan gibi düşün. Evin planı var — kaç oda, nereye kapı — ama planın kendisinde yaşayamazsın. Önce inşa etmen lazım. Abstract sınıf da bu: bir plan, bir şablon. Subclass'lar bu planı tamamlayarak somut (concrete) sınıflar oluşturur.
Abstract Class Sözdizimi
abstract class Shape {
String color;
Shape(String color) {
this.color = color;
}
// Abstract metot — gövdesi yok, subclass ZORUNLU implement etmeli
abstract double area();
// Concrete metot — gövdesi var, aynen kullanılabilir
void display() {
System.out.println(color + " shape, area: " + area());
}
}// Shape s = new Shape("Red"); // HATA! Abstract sınıftan nesne oluşturulamaz
class Circle extends Shape {
double radius;
Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
double area() { // ZORUNLU — abstract metodu implement et
return Math.PI * radius * radius;
}
}Shape s = new Circle("Red", 5); // Upcasting OK
s.display(); // Red shape, area: 78.53981633974483Dikkat et: display() metodu area() çağırıyor. area() abstract ama çalışma zamanında Circle'ın implementasyonu devreye giriyor. Bu polimorfizmin güzel bir örneği.
Abstract Metot Kuralları
Abstract metotların bazı kuralları var:
Abstract metot sadece abstract sınıfta olabilir.
Abstract metotun gövdesi yoktur — sadece imza.
Bir sınıf abstract sınıfı extend ederse, tüm abstract metotları implement etmelidir.
Implement etmezse, o sınıf da abstract olmalıdır.
abstract class Animal {
abstract void makeSound();
abstract void move();
}
// Tüm abstract metotları implement etti — concrete sınıf
class Dog extends Animal {
@Override
void makeSound() { System.out.println("Hav!"); }
@Override
void move() { System.out.println("Running"); }
}
// Sadece birini implement etti — hala abstract
abstract class Fish extends Animal {
@Override
void move() { System.out.println("Swimming"); }
// makeSound() implement edilmedi → Fish de abstract
}
// Fish'in kalan abstract metodunu implement etti
class Goldfish extends Fish {
@Override
void makeSound() { System.out.println("Blub blub"); }
}Abstract vs Concrete Sınıf
| Özellik | Abstract Sınıf | Concrete Sınıf |
|---|---|---|
new ile nesne | ❌ Oluşturulamaz | ✅ Oluşturulabilir |
| Abstract metot | ✅ Olabilir | ❌ Olamaz |
| Concrete metot | ✅ Olabilir | ✅ Olabilir |
| Constructor | ✅ Olabilir | ✅ Olabilir |
| Field | ✅ Olabilir | ✅ Olabilir |
Abstract sınıf "yarı tamamlanmış" bir sınıftır. Bazı şeyler hazır, bazıları alt sınıfa bırakılmış.
abstract class DatabaseConnection {
String url;
DatabaseConnection(String url) {
this.url = url;
}
// Ortak davranış — tüm veritabanları için aynı
void logConnection() {
System.out.println("Connecting to: " + url);
}
// Her veritabanı farklı bağlantı kurar
abstract void connect();
abstract void disconnect();
}
class MySqlConnection extends DatabaseConnection {
MySqlConnection(String url) { super(url); }
@Override
void connect() {
logConnection();
System.out.println("MySQL connected via JDBC");
}
@Override
void disconnect() {
System.out.println("MySQL disconnected");
}
}
class MongoConnection extends DatabaseConnection {
MongoConnection(String url) { super(url); }
@Override
void connect() {
logConnection();
System.out.println("MongoDB connected via driver");
}
@Override
void disconnect() {
System.out.println("MongoDB disconnected");
}
}logConnection() ortak — tekrar yazmana gerek yok. Ama connect() ve disconnect() her veritabanı için farklı.
Abstract Sınıf Constructor'ları
Abstract sınıftan nesne oluşturulamaz ama constructor'ı olabilir. Neden? Çünkü subclass'lar super() ile bu constructor'ı çağırır.
abstract class Vehicle {
String brand;
int year;
Vehicle(String brand, int year) {
this.brand = brand;
this.year = year;
}
abstract double fuelEfficiency();
}
class ElectricCar extends Vehicle {
double batteryCapacity;
ElectricCar(String brand, int year, double battery) {
super(brand, year); // Abstract sınıfın constructor'ını çağır
this.batteryCapacity = battery;
}
@Override
double fuelEfficiency() {
return batteryCapacity / 100 * 15; // km per kWh
}
}💡 İpucu: Abstract sınıfın constructor'ı ortak alanları initialize eder. Subclass'lar
super()ile bu ortak initialization'dan faydalanır.
Composition Nedir?
Composition, bir sınıfın başka bir sınıfı alan olarak içermesidir. Kalıtım "IS-A" ilişkisi kurarken, composition "HAS-A" ilişkisi kurar.
// Kalıtım yaklaşımı — KÖTÜ
// class Car extends Engine { } // Araba bir motor MÜ? Hayır!
// Composition yaklaşımı — İYİ
class Engine {
int horsepower;
Engine(int hp) {
this.horsepower = hp;
}
void start() {
System.out.println("Engine started: " + horsepower + " HP");
}
}
class Car {
Engine engine; // Car HAS-A Engine
String brand;
Car(String brand, Engine engine) {
this.brand = brand;
this.engine = engine;
}
void start() {
System.out.println(brand + " starting...");
engine.start(); // Delegasyon
}
}Engine v8 = new Engine(450);
Car car = new Car("Ford Mustang", v8);
car.start();
// Ford Mustang starting...
// Engine started: 450 HPCar sınıfı Engine'i içeriyor ve işi ona delege ediyor. Araba motoru "kullanıyor", motor "olmak" zorunda değil.
Composition vs Inheritance
Bu yazılım tasarımının en önemli tartışmalarından biri. Genel kural: "Favor composition over inheritance" — kalıtım yerine composition'ı tercih et.
Kalıtım sorunları:
Sıkı bağlılık (tight coupling): Superclass değişirse tüm subclass'lar etkilenir.
Kırılgan temel sınıf problemi: Superclass'a eklenen metot subclass'ı beklenmedik şekilde bozabilir.
Tek kalıtım limiti: Java'da bir sınıf sadece bir sınıftan extend edebilir.
Gereksiz miras: Subclass, superclass'ın kullanmadığı metotları da devralır.
Composition avantajları:
Gevşek bağlılık: Bileşenler bağımsız değiştirilebilir.
Esneklik: Runtime'da bileşen değiştirilebilir.
Tekrar kullanım: Aynı bileşen farklı sınıflarda kullanılabilir.
Test kolaylığı: Bileşenler bağımsız test edilebilir.
Bir karşılaştırma örneği:
// KALITIM ile — katı
class Robot extends Walker {
// Robot her zaman yürür, başka hareket edemez
}
// COMPOSITION ile — esnek
class Robot {
MovementStrategy movement;
Robot(MovementStrategy movement) {
this.movement = movement;
}
void move() {
movement.execute();
}
void changeMovement(MovementStrategy newMovement) {
this.movement = newMovement; // Runtime'da değiştir!
}
}Composition ile robot başta yürür, sonra uçmaya geçebilir. Kalıtım ile robot Walker'a bağımlıdır, değiştiremezsin.
Gerçekçi Örnek: Bildirim Sistemi
Kalıtım ve composition'ı birlikte kullanan bir örnek:
// Composition bileşeni — mesaj formatlayıcı
class MessageFormatter {
String format(String message) {
return "[" + java.time.LocalTime.now() + "] " + message;
}
}
// Composition bileşeni — log kaydedici
class Logger {
void log(String action) {
System.out.println("LOG: " + action);
}
}
// Abstract sınıf — ortak yapıyı tanımlar
abstract class NotificationSender {
MessageFormatter formatter; // HAS-A
Logger logger; // HAS-A
NotificationSender() {
this.formatter = new MessageFormatter();
this.logger = new Logger();
}
void send(String message) {
String formatted = formatter.format(message);
doSend(formatted);
logger.log("Sent: " + formatted);
}
abstract void doSend(String message); // Alt sınıf implement eder
}
class EmailSender extends NotificationSender {
@Override
void doSend(String message) {
System.out.println("Email: " + message);
}
}
class SmsSender extends NotificationSender {
@Override
void doSend(String message) {
System.out.println("SMS: " + message);
}
}Burada kalıtım (abstract class) ve composition (formatter, logger) birlikte kullanılıyor. NotificationSender ortak akışı tanımlıyor, bileşenleri kullanıyor, detayları subclass'a bırakıyor.
Ne Zaman Kalıtım, Ne Zaman Composition?
Kalıtımı kullan:
Gerçek bir IS-A ilişkisi varsa (
Cat IS-A Animal)Subclass, superclass'ın tüm davranışlarını mantıklı şekilde taşıyorsa
Polimorfik davranış gerekiyorsa
Composition'ı kullan:
HAS-A ilişkisi varsa (
Car HAS-A Engine)Sadece bazı davranışları yeniden kullanmak istiyorsan
Bileşenin runtime'da değişebilmesi gerekiyorsa
Birden fazla "yetenek" birleştirmek istiyorsan
// IS-A → Kalıtım uygun
class SavingsAccount extends BankAccount { }
// HAS-A → Composition uygun
class Car {
Engine engine;
GpsSystem gps;
AudioSystem audio;
}
// Hibrit — ikisi birlikte
abstract class Game {
Renderer renderer; // composition
SoundEngine sound; // composition
abstract void play(); // kalıtım
}⚠️ Dikkat: "Kalıtım mı composition mı?" sorusunun cevabı her zaman "composition" değildir. Ama emin olamıyorsan, composition'la başla. Daha sonra kalıtıma geçmek, tam tersinden çok daha kolaydır.
Kırılgan Temel Sınıf Problemi
Kalıtımın en bilinen sorunu. Superclass'ta masum görünen bir değişiklik, subclass'ları bozabilir.
class MyList {
int count = 0;
void add(Object item) {
count++;
// listeye ekle...
}
void addAll(Object[] items) {
for (Object item : items) {
add(item); // add() metodu count'u artırıyor
}
}
}
class CountingList extends MyList {
int addCount = 0;
@Override
void add(Object item) {
addCount++;
super.add(item);
}
}CountingList list = new CountingList();
list.addAll(new Object[]{"a", "b", "c"});
System.out.println(list.addCount); // 3 — beklenenŞimdi MyList'in addAll() metodunu değiştirdiğini düşün:
// MyList'te değişiklik — addAll artık add() çağırmıyor
void addAll(Object[] items) {
count += items.length;
// toplu ekleme...
}Bu durumda CountingList.addCount sıfır kalır! Superclass'taki bir değişiklik subclass'ı sessizce bozdu. Composition kullansaydın bu sorun olmazdı çünkü iç implementasyona bağımlı değilsin.
Delegation Pattern
Composition ile birlikte kullanılan yaygın bir pattern. Bir sınıf işi başka bir nesneye delege eder.
class Printer {
void print(String text) {
System.out.println(text);
}
}
class Report {
private Printer printer;
private String content;
Report(String content, Printer printer) {
this.content = content;
this.printer = printer;
}
void printReport() {
printer.print("=== REPORT ===");
printer.print(content);
printer.print("===============");
}
}Report yazdırma işini bilmiyor, Printer'a delege ediyor. Yarın Printer yerine PdfPrinter geçsen, Report sınıfına dokunmazsın.
Abstract Sınıf Kullanım Kalıpları
1. Template Method Pattern:
abstract class DataProcessor {
// Template method — iskelet
final void process() {
readData();
transformData();
writeData();
}
abstract void readData();
abstract void transformData();
abstract void writeData();
}
class CsvProcessor extends DataProcessor {
@Override
void readData() { System.out.println("Reading CSV..."); }
@Override
void transformData() { System.out.println("Parsing CSV fields..."); }
@Override
void writeData() { System.out.println("Writing to database..."); }
}2. Ortak kod + özelleştirme:
abstract class HttpHandler {
void handle(String request) {
log(request); // Ortak
authenticate(); // Ortak
processRequest(request); // Alt sınıf özelleştirir
}
private void log(String req) {
System.out.println("Request: " + req);
}
private void authenticate() {
System.out.println("Authenticated");
}
abstract void processRequest(String request);
}💡 İpucu: Abstract sınıf, "bazı davranışlar ortak, bazıları farklı" durumlarında mükemmeldir. Ortak olanları concrete metot, farklı olanları abstract metot olarak tanımla.
Özet
Abstract sınıf,
abstractkeyword'ü ile tanımlanır, doğrudan nesne oluşturulamaz; hem abstract (gövdesiz) hem concrete (gövdeli) metotlar içerebilir.Bir subclass abstract sınıfı extend ederse tüm abstract metotları implement etmek zorundadır; yoksa kendisi de abstract olmalıdır.
Composition (HAS-A), bir sınıfın başka sınıfları alan olarak içermesidir; kalıtıma göre daha esnek ve gevşek bağlıdır.
"Favor composition over inheritance" — gerçek bir IS-A ilişkisi yoksa composition'ı tercih et.
Kalıtım sıkı bağlılık yaratır ve kırılgan temel sınıf problemine yol açabilir; composition bu riskleri azaltır.
Pratikte ikisi birlikte kullanılır: abstract sınıf ortak yapıyı tanımlar, composition bileşenlerle esneklik sağlar.
AI Asistan
Sorularını yanıtlamaya hazır