🎯 0.10: Python Sınıflar

Algoritmalar ve Veri Yapıları Dersi - Object-Oriented Programming (OOP)

🌟 Nesne Yönelimli Programlama (OOP) Nedir?

Object-Oriented Programming (OOP), programlamayı gerçek dünya nesneleri gibi düşünerek organize etme yaklaşımıdır. Her nesne (obje) hem veri (özellikler) hem de davranış (metodlar) içerir.

🤔 Neden Sınıf Kullanmalıyız?

Bir banka uygulaması yazıyorsun ve 1000 müşteri hesabını yönetmen gerekiyor...

❌ SINIFSIZ (Kabus!)

# Her hesap için ayrı değişkenler 😱
hesap1_sahibi = "Ali"
hesap1_bakiye = 1000
hesap1_hesap_no = "TR001"

hesap2_sahibi = "Ayşe"  
hesap2_bakiye = 2500
hesap2_hesap_no = "TR002"

# Para çekme işlemi - her hesap için ayrı!
def hesap1_para_cek(miktar):
    global hesap1_bakiye
    if hesap1_bakiye >= miktar:
        hesap1_bakiye -= miktar
        
def hesap2_para_cek(miktar):
    global hesap2_bakiye
    if hesap2_bakiye >= miktar:
        hesap2_bakiye -= miktar

# 1000 müşteri = 1000 fonksiyon??? 🤯

⚠️ Sorunlar:

  • Her müşteri için ayrı değişkenler
  • Her müşteri için ayrı fonksiyonlar
  • Veri ve işlem birbirinden kopuk
  • Yönetilemez karmaşıklık!

✅ SINIFLI (Harika!)

class BankaHesabi:
    def __init__(self, sahibi, bakiye):
        self.sahibi = sahibi
        self.bakiye = bakiye
    
    def para_cek(self, miktar):
        if self.bakiye >= miktar:
            self.bakiye -= miktar
            return True
        return False

# Şimdi istediğin kadar hesap oluştur!
hesap1 = BankaHesabi("Ali", 1000)
hesap2 = BankaHesabi("Ayşe", 2500)
hesap3 = BankaHesabi("Mehmet", 5000)

# Hepsi aynı metodları kullanır
hesap1.para_cek(200)
hesap2.para_cek(500)
# 1000 müşteri? Sadece 1000 obje! ✨

✅ Avantajlar:

  • Şablon bir kez yazılır, sınırsız kullan
  • Veri + işlem bir arada (encapsulation)
  • Gerçek dünyayı modeller
  • Kolay yönetim ve genişletme

🏗️ Sınıf ve Obje İlişkisi

graph TD A[Sınıf: Araba
Blueprint/Şablon] --> B[Obje 1: Kırmızı BMW] A --> C[Obje 2: Mavi Audi] A --> D[Obje 3: Siyah Mercedes] B --> B1[marka: BMW
renk: Kırmızı
hız: 0] C --> C1[marka: Audi
renk: Mavi
hız: 0] D --> D1[marka: Mercedes
renk: Siyah
hız: 0] style A fill:#f5576c,color:#fff style B fill:#28a745,color:#fff style C fill:#28a745,color:#fff style D fill:#28a745,color:#fff

Sınıf = Kurabiye kalıbı 🍪 | Obje = O kalıpla yapılan kurabiyeler

🏛️ OOP'nin 4 Temel İlkesi

Nesne yönelimli programlamanın temelini oluşturan dört prensip:

📦 1. Encapsulation (Kapsülleme)

Veri ve metodları bir arada tutma. Dışarıdan erişimi kontrol etme.

class Hesap:
    def __init__(self):
        self.__bakiye = 0  # Gizli!
    
    def bakiye_goster(self):
        return self.__bakiye

🎭 2. Abstraction (Soyutlama)

Karmaşık detayları gizleyip, sadece gerekli arayüzü sunma.

# Kullanıcı sadece bunu görür:
araba.calistir()

# Motor, akü, yakıt sistemi...
# detayları gizli kalır

🧬 3. Inheritance (Kalıtım)

Bir sınıfın özelliklerini başka sınıflara aktarma.

class Hayvan:
    def ses_cikar(self): pass

class Kopek(Hayvan):
    def ses_cikar(self):
        print("Hav hav!")

🔀 4. Polymorphism (Çok Biçimlilik)

Aynı metodun farklı sınıflarda farklı davranması.

def ses_cikardir(hayvan):
    hayvan.ses_cikar()

ses_cikardir(kopek)  # Hav hav!
ses_cikardir(kedi)   # Miyav!
💡 Bu Derste: Encapsulation ve Abstraction temellerini öğreniyoruz. Inheritance ve Polymorphism ileri seviye konulardır, ilerleyen derslerde göreceğiz.
🎯 Temel OOP Kavramları:
  • Sınıf (Class): Nesnelerin şablonu/taslağı. Bir nesnenin nasıl olacağını tanımlar
  • Obje/Nesne (Object): Sınıftan oluşturulan gerçek örnekler (instance)
  • Constructor (__init__): Obje oluşturulurken çalışan özel metod
  • self: Objenin kendisini temsil eden referans (Python'a özgü)
  • Instance Variable: Her objenin kendi verileri (özellikler)
  • Instance Method: Objenin davranışları (fonksiyonlar)

🌍 Gerçek Dünya

Araba:

  • Özellikler: Marka, model, renk, hız
  • Davranışlar: Hızlan, yavaşla, dur, dön

💻 Python'da

Araba Sınıfı:

  • Özellikler: Instance variables
  • Davranışlar: Instance methods

🔨 İlk Sınıfımızı Oluşturalım

Basit Bir Sınıf - Köpek

🐍 Python Kodu
class Kopek:
    """Köpek sınıfı - basit bir örnek"""

    # Constructor - obje oluşturulurken çalışır
    def __init__(self, isim, yas):
        self.isim = isim  # Instance variable
        self.yas = yas    # Instance variable
        print(f"Yeni bir köpek oluşturuldu: {isim}")

    # Instance method
    def havla(self):
        print(f"{self.isim}: Hav hav!")

    def bilgi_goster(self):
        print(f"İsim: {self.isim}, Yaş: {self.yas}")

# Obje oluşturma (instantiation)
kopek1 = Kopek("Karabaş", 3)
kopek2 = Kopek("Pamuk", 5)

print("\n--- Köpek 1 ---")
kopek1.bilgi_goster()
kopek1.havla()

print("\n--- Köpek 2 ---")
kopek2.bilgi_goster()
kopek2.havla()
⚡ Önemli Noktalar:
self: Metodların ilk parametresi her zaman self'tir. Objenin kendisini temsil eder.
__init__: Constructor metodu, obje oluşturulurken otomatik çalışır.
self.değişken: Instance variable tanımlaması. Her objenin kendi kopyası vardır.

📐 Pratik Örnek: Dikdörtgen Sınıfı

🐍 Python Kodu
class Dikdortgen:
    """Dikdörtgen sınıfı"""

    def __init__(self, genislik, yukseklik):
        """Constructor - dikdörtgen oluşturur"""
        self.genislik = genislik
        self.yukseklik = yukseklik

    def alan_hesapla(self):
        """Dikdörtgenin alanını hesaplar"""
        return self.genislik * self.yukseklik

    def cevre_hesapla(self):
        """Dikdörtgenin çevresini hesaplar"""
        return 2 * (self.genislik + self.yukseklik)

    def kare_mi(self):
        """Dikdörtgenin kare olup olmadığını kontrol eder"""
        return self.genislik == self.yukseklik

    def bilgi_yazdir(self):
        """Dikdörtgen bilgilerini yazdırır"""
        print(f"Dikdörtgen:")
        print(f"  Genişlik: {self.genislik}")
        print(f"  Yükseklik: {self.yukseklik}")
        print(f"  Alan: {self.alan_hesapla()}")
        print(f"  Çevre: {self.cevre_hesapla()}")
        print(f"  Kare mi? {'Evet' if self.kare_mi() else 'Hayır'}")

# Dikdörtgen objeleri oluştur
dikdortgen1 = Dikdortgen(5, 10)
dikdortgen2 = Dikdortgen(7, 7)

print("=== Dikdörtgen 1 ===")
dikdortgen1.bilgi_yazdir()

print("\n=== Dikdörtgen 2 ===")
dikdortgen2.bilgi_yazdir()

🔢 Kesir Sınıfı

Matematiksel kesir (oran) işlemlerini yapan bir sınıf yazalım:

🐍 Python Kodu
def ebob_hesapla(a, b):
    """En Büyük Ortak Bölen (Öklid algoritması)"""
    while b:
        a, b = b, a % b
    return a

class Kesir:
    """Matematiksel kesir sınıfı"""

    def __init__(self, pay, payda):
        """Kesir oluştur"""
        if payda == 0:
            print("HATA: Payda sıfır olamaz!")
            self.pay = 0
            self.payda = 1
        else:
            self.pay = pay
            self.payda = payda
            self.sadelestir()

    def sadelestir(self):
        """Kesri sadeleştirir (EBOB ile)"""
        ebob = ebob_hesapla(abs(self.pay), abs(self.payda))
        self.pay //= ebob
        self.payda //= ebob

        # Payda negatifse işareti paya taşı
        if self.payda < 0:
            self.pay = -self.pay
            self.payda = -self.payda

    def goster(self):
        """Kesri string olarak döndürür"""
        if self.payda == 1:
            return str(self.pay)
        return f"{self.pay}/{self.payda}"

    def ondalik(self):
        """Kesrin ondalık değerini döndürür"""
        return self.pay / self.payda

    def topla(self, diger):
        """İki kesri toplar"""
        yeni_pay = self.pay * diger.payda + diger.pay * self.payda
        yeni_payda = self.payda * diger.payda
        return Kesir(yeni_pay, yeni_payda)

    def cikar(self, diger):
        """İki kesri çıkarır"""
        yeni_pay = self.pay * diger.payda - diger.pay * self.payda
        yeni_payda = self.payda * diger.payda
        return Kesir(yeni_pay, yeni_payda)

    def carp(self, diger):
        """İki kesri çarpar"""
        yeni_pay = self.pay * diger.pay
        yeni_payda = self.payda * diger.payda
        return Kesir(yeni_pay, yeni_payda)

    def bol(self, diger):
        """Birinci kesri ikinciye böler"""
        yeni_pay = self.pay * diger.payda
        yeni_payda = self.payda * diger.pay
        return Kesir(yeni_pay, yeni_payda)

# Kesir objeleri oluştur
k1 = Kesir(3, 4)
k2 = Kesir(2, 5)
k3 = Kesir(6, 8)  # Sadeleştirilecek -> 3/4

print("Kesir Örnekleri:")
print(f"k1 = {k1.goster()} = {k1.ondalik():.4f}")
print(f"k2 = {k2.goster()} = {k2.ondalik():.4f}")
print(f"k3 = {k3.goster()} (6/8 sadeleştirildi)")

# İşlemler
print("\n=== İşlemler ===")
toplam = k1.topla(k2)
print(f"{k1.goster()} + {k2.goster()} = {toplam.goster()}")

fark = k1.cikar(k2)
print(f"{k1.goster()} - {k2.goster()} = {fark.goster()}")

carpim = k1.carp(k2)
print(f"{k1.goster()} × {k2.goster()} = {carpim.goster()}")

bolum = k1.bol(k2)
print(f"{k1.goster()} ÷ {k2.goster()} = {bolum.goster()}")

# Zincirleme işlem
k4 = Kesir(1, 2)
k5 = Kesir(1, 3)
k6 = Kesir(1, 6)
sonuc = k4.topla(k5).topla(k6)
print(f"\n1/2 + 1/3 + 1/6 = {sonuc.goster()}")
✅ Kesir Sınıfının Özellikleri:
  • Otomatik sadeleştirme (EBOB kullanarak)
  • Dört işlem: toplama, çıkarma, çarpma, bölme
  • Ondalık değer hesaplama
  • Zincirleme işlem desteği (method chaining)
  • Payda sıfır kontrolü

🥧 Kesir Gösterimi (Pasta Grafiği)

△ Üçgen Sınıfı

🐍 Python Kodu
class Ucgen:
    """Üçgen sınıfı - üç kenar uzunluğu ile tanımlanır"""

    def __init__(self, a, b, c):
        """Üçgen oluştur"""
        self.a = a
        self.b = b
        self.c = c

        if not self.gecerli_mi():
            print("UYARI: Bu kenar uzunlukları geçerli bir üçgen oluşturmaz!")

    def gecerli_mi(self):
        """Üçgen eşitsizliğini kontrol eder"""
        return (self.a + self.b > self.c and
                self.a + self.c > self.b and
                self.b + self.c > self.a)

    def cevre(self):
        """Üçgenin çevresini hesaplar"""
        return self.a + self.b + self.c

    def alan(self):
        """Üçgenin alanını hesaplar (Heron formülü)"""
        if not self.gecerli_mi():
            return 0

        s = self.cevre() / 2
        alan = (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5
        return alan

    def dik_mi(self):
        """Üçgenin dik olup olmadığını kontrol eder"""
        kenarlar = sorted([self.a, self.b, self.c])
        return abs(kenarlar[0]**2 + kenarlar[1]**2 - kenarlar[2]**2) < 0.0001

    def bilgi_yazdir(self):
        """Üçgen bilgilerini yazdırır"""
        print(f"Üçgen Kenarları: a={self.a}, b={self.b}, c={self.c}")
        if self.gecerli_mi():
            print(f"Çevre: {self.cevre():.2f}")
            print(f"Alan: {self.alan():.2f}")
            print(f"Dik üçgen mi? {'Evet' if self.dik_mi() else 'Hayır'}")

# Test
print("=== Dik Üçgen (3-4-5) ===")
ucgen1 = Ucgen(3, 4, 5)
ucgen1.bilgi_yazdir()

print("\n=== Normal Üçgen ===")
ucgen2 = Ucgen(7, 8, 9)
ucgen2.bilgi_yazdir()

🎓 Öğrenci Bilgi Sistemi

🐍 Python Kodu
class Ogrenci:
    """Öğrenci bilgi sistemi sınıfı"""

    # Class variable - tüm öğrenciler için ortak
    okul_adi = "Algoritma Üniversitesi"

    def __init__(self, ad, soyad, numara):
        """Öğrenci oluştur"""
        self.ad = ad
        self.soyad = soyad
        self.numara = numara
        self.notlar = []

    def not_ekle(self, ders, not_degeri):
        """Öğrenciye not ekler"""
        if 0 <= not_degeri <= 100:
            self.notlar.append({'ders': ders, 'not': not_degeri})
            print(f"✓ {ders}: {not_degeri} eklendi")
        else:
            print("❌ Not 0-100 arasında olmalı!")

    def ortalama(self):
        """Not ortalamasını hesaplar"""
        if len(self.notlar) == 0:
            return 0
        toplam = sum(n['not'] for n in self.notlar)
        return toplam / len(self.notlar)

    def rapor(self):
        """Öğrenci raporunu gösterir"""
        print("=" * 50)
        print(f"ÖĞRENCI RAPORU - {Ogrenci.okul_adi}")
        print("=" * 50)
        print(f"Ad Soyad: {self.ad} {self.soyad}")
        print(f"Numara: {self.numara}")
        print("-" * 50)
        for n in self.notlar:
            print(f"  {n['ders']}: {n['not']}")
        print("-" * 50)
        if len(self.notlar) > 0:
            print(f"Ortalama: {self.ortalama():.2f}")
        print("=" * 50)

# Test
ogrenci1 = Ogrenci("Ahmet", "Yılmaz", "2021001")
ogrenci1.not_ekle("Algoritmalar", 85)
ogrenci1.not_ekle("Veri Yapıları", 92)
ogrenci1.not_ekle("Veritabanı", 78)

print()
ogrenci1.rapor()

🏦 Pratik Örnek: Banka Hesabı

stateDiagram-v2 [*] --> HesapAçıldı: Yeni Hesap HesapAçıldı --> ParaYatır: para_yatir() ParaYatır --> ParaÇek: para_cek() ParaÇek --> ParaYatır: para_yatir() ParaYatır --> [*]: Hesap Kapatıldı ParaÇek --> [*]: Hesap Kapatıldı
🐍 Python Kodu
class BankaHesabi:
    """Basit banka hesabı sınıfı"""

    def __init__(self, hesap_sahibi, bakiye=0):
        """Banka hesabı oluştur"""
        self.hesap_sahibi = hesap_sahibi
        self.bakiye = bakiye
        print(f"✓ Hesap oluşturuldu: {hesap_sahibi}")

    def para_yatir(self, miktar):
        """Hesaba para yatırır"""
        if miktar <= 0:
            print("❌ Miktar pozitif olmalı!")
            return False

        self.bakiye += miktar
        print(f"✓ {miktar} TL yatırıldı. Yeni bakiye: {self.bakiye} TL")
        return True

    def para_cek(self, miktar):
        """Hesaptan para çeker"""
        if miktar <= 0:
            print("❌ Miktar pozitif olmalı!")
            return False

        if miktar > self.bakiye:
            print(f"❌ Yetersiz bakiye! Mevcut: {self.bakiye} TL")
            return False

        self.bakiye -= miktar
        print(f"✓ {miktar} TL çekildi. Yeni bakiye: {self.bakiye} TL")
        return True

    def bakiye_goster(self):
        """Bakiyeyi gösterir"""
        print(f"{self.hesap_sahibi} - Bakiye: {self.bakiye} TL")

# Test
hesap1 = BankaHesabi("Ahmet Yılmaz", 1000)
print()

hesap1.bakiye_goster()
print()

hesap1.para_yatir(500)
hesap1.para_cek(200)
print()

hesap1.para_cek(5000)  # Yetersiz bakiye
print()

hesap1.bakiye_goster()

⚖️ Fonksiyon vs Sınıf: Ne Zaman Hangisi?

Bu en çok sorulan sorulardan biri! İşte karar vermenize yardımcı olacak pratik kurallar:

📚 Fonksiyon Kullan

Ne zaman?
  • ✅ Tekil, bağımsız işlemler
  • ✅ Girdi al → İşle → Çıktı ver
  • ✅ Durum (state) saklamaya gerek yok
  • ✅ Matematiksel hesaplamalar
  • ✅ Yardımcı (utility) işlemler
Örnek Durumlar:
# ✅ Fonksiyon için uygun
def faktoriyel(n):
    return 1 if n <= 1 else n * faktoriyel(n-1)

def ortalama_bul(liste):
    return sum(liste) / len(liste)

def metin_temizle(metin):
    return metin.strip().lower()

🎯 Sınıf Kullan

Ne zaman?
  • ✅ Veri + davranış birlikte
  • ✅ Durum (state) saklamak gerekli
  • ✅ Aynı şeyden birden fazla örnek
  • ✅ Gerçek dünya nesnelerini modelleme
  • ✅ Veri yapıları (Stack, Queue, LinkedList...)
Örnek Durumlar:
# ✅ Sınıf için uygun
class Ogrenci:
    def __init__(self, ad, notlar):
        self.ad = ad
        self.notlar = notlar  # durum!
    
    def ortalama(self):
        return sum(self.notlar) / len(self.notlar)
    
    def not_ekle(self, yeni_not):
        self.notlar.append(yeni_not)  # durum değişir!

🎯 Karar Rehberi: Fonksiyon mu Sınıf mı?

❓ Soru 1: İşlem veri saklayacak mı?
(İşlem bittikten sonra hatırlanması gereken değerler var mı?)
❌ Hayır
Sadece hesapla ve döndür
✅ FONKSİYON
✓ Evet
Durum bilgisi tutulacak
↓ Soru 2'ye geç
❓ Soru 2: Aynı veriye birden fazla işlem yapılacak mı?
(Veriyi oku, güncelle, sorgula gibi farklı işlemler?)
❌ Hayır
Tek seferlik işlem
✅ FONKSİYON
✓ Evet
Veri üzerinde çoklu işlem
↓ Soru 3'e geç
❓ Soru 3: Bu yapıdan birden fazla örnek olacak mı?
(Birden fazla öğrenci, hesap, araba vs.?)
❌ Hayır
Tek instance yeterli
📦 Modül veya Dict
✓ Evet
Çok sayıda örnek gerekli
✅ SINIF

🎮 Pratik Senaryo: Oyun Karakteri

❌ Fonksiyonlarla (Zor!)
# Her karakter için ayrı değişken
karakter1_isim = "Savaşçı"
karakter1_can = 100
karakter1_guc = 50

karakter2_isim = "Büyücü"
karakter2_can = 70
karakter2_guc = 80

def karakter1_saldir():
    # karakter2'ye zarar ver
    pass

# Karmaşık ve yönetilemez!
✅ Sınıfla (Kolay!)
class Karakter:
    def __init__(self, isim, can, guc):
        self.isim = isim
        self.can = can
        self.guc = guc
    
    def saldir(self, hedef):
        hedef.can -= self.guc
        print(f"{self.isim} saldırdı!")

k1 = Karakter("Savaşçı", 100, 50)
k2 = Karakter("Büyücü", 70, 80)
k1.saldir(k2)  # Temiz ve anlaşılır!
💡 Altın Kural:

"Veri + O veriye yapılan işlemler" birlikte geziyorsa → Sınıf
"Girdi → İşlem → Çıktı" tek seferlik ise → Fonksiyon