Polimorfizm ve Duck Typing
OOP'nin en zarif prensiplerinden biri olan polimorfizm, kodunun esnekliğini ve genişletilebilirliğini dramatik şekilde artırır. Bu kavramı gerçekten anladığında, yazılım tasarımına bakış açın değişecek.
Polimorfizm Nedir?
Polimorfizm kelimesi Yunanca'dan gelir: "poly" (çok) + "morph" (biçim) = çok biçimlilik. Programlama terimlerinde: aynı arayüz (interface), farklı davranış.
Evrensel Uzaktan Kumanda Analojisi 🎮
Evindeki cihazları düşün: TV, klima, müzik sistemi. Hepsinin "aç/kapat" düğmesi var. Ama her birinde "aç" farklı bir şey yapar:
TV'de → ekran açılır
Klimada → soğutucu çalışır
Müzik sisteminde → şarkı çalmaya başlar
Aynı komut ("aç"), farklı davranış. İşte polimorfizm bu.
class TV:
def turn_on(self):
return "📺 Ekran açılıyor, kanal yükleniyor..."
class AC:
def turn_on(self):
return "❄️ Soğutucu çalışıyor, 24°C ayarlandı..."
class MusicPlayer:
def turn_on(self):
return "🎵 Müzik çalmaya başlıyor..."
# Aynı arayüz, farklı davranış
devices = [TV(), AC(), MusicPlayer()]
for device in devices:
print(device.turn_on())Çıktı:
📺 Ekran açılıyor, kanal yükleniyor...
❄️ Soğutucu çalışıyor, 24°C ayarlandı...
🎵 Müzik çalmaya başlıyor...Dikkat et: turn_on() çağırırken hangi nesne olduğunu kontrol etmiyoruz. Her nesne kendi versiyonunu çalıştırıyor. Bu, polimorfizmin gücü.
Duck Typing: "Ördek Gibi Yürüyorsa..."
Python'da polimorfizm duck typing prensibiyle çalışır. Bu prensip şöyle der:
"Eğer bir şey ördek gibi yürüyor ve ördek gibi vaklıyorsa, o bir ördektir."
Yani Python, bir nesnenin tipine değil, davranışına bakar. Eğer nesnenin beklenen metodu varsa, o nesneyi kullanabilirsin — hangi sınıftan geldiği önemli değil.
class Duck:
def walk(self):
return "🦆 Paytak paytak yürüyor"
def quack(self):
return "🦆 Vak vak!"
class Person:
def walk(self):
return "🚶 İki ayak üstünde yürüyor"
def quack(self):
return "🗣️ 'Vak vak!' diyor (taklit)"
class Robot:
def walk(self):
return "🤖 Mekanik olarak yürüyor"
def quack(self):
return "🔊 'Vak vak' ses dosyası çalıyor"
def duck_test(entity):
"""Ördek testi — tipi kontrol etmez, davranışa bakar."""
print(entity.walk())
print(entity.quack())
print()
# Hepsi çalışır — tip önemli değil, davranış önemli!
duck_test(Duck())
duck_test(Person())
duck_test(Robot())Python, duck_test fonksiyonunda entity'nin Duck olup olmadığını kontrol etmez. Sadece walk() ve quack() metodlarının var olmasını bekler. Bu, Python'un duck typing yaklaşımıdır.
Duck Typing vs Statik Tip Kontrolü
# Java/C# tarzı — tip kontrolü gerektirir (anti-pattern Python'da)
def make_sound_java_style(animal):
if isinstance(animal, Dog):
animal.bark()
elif isinstance(animal, Cat):
animal.meow()
elif isinstance(animal, Bird):
animal.chirp()
# Yeni hayvan eklersen buraya da eklemen lazım! ❌
# Python tarzı — duck typing
def make_sound_python_style(animal):
animal.speak() # Herkes speak() implement etsin, bitti ✅
# Yeni hayvan eklersen bu fonksiyon DEĞİŞMEZ!💡 İpucu: Duck typing'in en büyük avantajı Açık-Kapalı Prensibi (Open-Closed Principle) doğal olarak uygulamasıdır: Kodun yeni türlere açık ama mevcut kodun değişikliğe kapalı. Yeni bir hayvan sınıfı eklediğinde
make_sound_python_stylefonksiyonuna dokunman gerekmez.
Method Overriding ile Polimorfizm
Polimorfizmin en yaygın uygulaması, kalıtım ve method overriding kombinasyonudur:
class Notification:
def __init__(self, message):
self.message = message
def send(self):
raise NotImplementedError("Alt sınıf send() implement etmeli!")
def preview(self):
return f"[{self.__class__.__name__}] {self.message}"
class Email(Notification):
def send(self):
return f"📧 Email gönderildi: {self.message}"
class SMS(Notification):
def send(self):
return f"📱 SMS gönderildi: {self.message[:160]}"
class Push(Notification):
def send(self):
return f"🔔 Push gönderildi: {self.message[:50]}"
class Slack(Notification):
def send(self):
return f"💬 Slack mesajı: {self.message}"
def send_all(notifications):
"""Tüm bildirimleri gönderir — tipe bakmaz!"""
for n in notifications:
print(n.send())
# Farklı tipler, aynı arayüz
notifications = [
Email("Hoş geldiniz!"),
SMS("Kodunuz: 4523"),
Push("Yeni mesajınız var"),
Slack("Deploy tamamlandı ✅"),
]
send_all(notifications)send_all fonksiyonu notifications listesindeki her nesnenin send() metodunu çağırıyor. Hangi tür bildirim olduğunu bilmiyor ve bilmesi de gerekmiyor. Yarın yeni bir WhatsApp bildirim sınıfı eklesen, send_all fonksiyonuna dokunman gerekmez.
len() Neden Her Yerde Çalışır?
Python'un built-in fonksiyonları da polimorfizm kullanır. len() fonksiyonunu düşün:
print(len("merhaba")) # 7 — str.__len__()
print(len([1, 2, 3])) # 3 — list.__len__()
print(len({"a": 1})) # 1 — dict.__len__()
print(len({1, 2, 3})) # 3 — set.__len__()
print(len(range(10))) # 10 — range.__len__()len() nasıl bu kadar farklı tipi destekliyor? Çünkü her tipin __len__ magic method'u var ve len() aslında bunu çağırıyor. Aynı şey str() → __str__, iter() → __iter__, bool() → __bool__ için de geçerli.
Protocol Kavramı
Python'da buna protocol denir. Bir protocol, belirli magic method'ları tanımlayan nesnelerin bir "arayüzü" gibidir:
| Protocol | Gerekli Method | Kullanım |
|---|---|---|
| Sized | __len__ | len(obj) |
| Iterable | __iter__ | for x in obj |
| Container | __contains__ | x in obj |
| Callable | __call__ | obj() |
| Hashable | __hash__ | hash(obj), set/dict key |
| Comparable | __eq__, __lt__ vb. | ==, <, > |
| Sequence | __getitem__ + __len__ | obj[i], len(obj) |
Bu protocol'ler sayesinde Python'un built-in fonksiyonları polimorfik çalışır. Kendi sınıfında __len__ tanımlarsan, len() senin sınıfınla da çalışır — kalıtıma bile gerek yok!
class Team:
def __init__(self, name, members):
self.name = name
self.members = members
def __len__(self):
return len(self.members)
def __contains__(self, person):
return person in self.members
def __iter__(self):
return iter(self.members)
def __getitem__(self, index):
return self.members[index]
team = Team("Backend", ["Ali", "Veli", "Ayşe"])
# Built-in fonksiyonlar çalışır!
print(len(team)) # 3
print("Ali" in team) # True
print(team[0]) # Ali
print(list(team)) # ['Ali', 'Veli', 'Ayşe']
# sorted bile çalışır
for member in sorted(team):
print(f" {member}")Protocol/Interface Düşüncesi Python'da
Statik tipli dillerde (Java, C#) interface'ler açıkça tanımlanır ve sınıf "implements" ile bunu belirtir. Python'da bu formalite yoktur — duck typing yeterlidir.
Ama Python 3.8+ ile typing.Protocol kullanarak açık interface tanımlayabilirsin. Bu zorunlu değil, ama tip kontrolcüler (mypy gibi) için faydalıdır:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawable(Protocol):
def draw(self) -> str: ...
class Circle:
def draw(self) -> str:
return "⭕ Daire çizildi"
class Square:
def draw(self) -> str:
return "⬜ Kare çizildi"
class Text:
def draw(self) -> str:
return "📝 Metin yazıldı"
# runtime_checkable ile isinstance kullanabilirsin
c = Circle()
print(isinstance(c, Drawable)) # True — draw() metodu var
# Hiçbir sınıf Drawable'dan miras almıyor!
# Ama draw() metodu var, bu yeter (structural subtyping)
def render_all(items: list[Drawable]):
for item in items:
print(item.draw())
render_all([Circle(), Square(), Text()])Bu, structural subtyping denir — sınıfın yapısı (hangi metodları var) önemli, sınıf hiyerarşisi değil.
Polimorfizm Desenleri
1. Strategy Pattern
Aynı işi farklı stratejilerle yapmak. Strateji değiştirmek, algoritma değiştirmek demek:
class PricingStrategy:
def calculate(self, base_price):
return base_price
class PercentageDiscount:
def __init__(self, percent):
self.percent = percent
def calculate(self, base_price):
return base_price * (1 - self.percent / 100)
class FixedDiscount:
def __init__(self, amount):
self.amount = amount
def calculate(self, base_price):
return max(0, base_price - self.amount)
class BuyOneGetOneFree:
def calculate(self, base_price):
return base_price / 2
class ShoppingCart:
def __init__(self, pricing=None):
self.items = []
self.pricing = pricing or PricingStrategy()
def add(self, item, price):
self.items.append((item, price))
def set_pricing(self, strategy):
"""Runtime'da strateji değiştir!"""
self.pricing = strategy
def total(self):
raw = sum(price for _, price in self.items)
return self.pricing.calculate(raw)
# Kullanım
cart = ShoppingCart()
cart.add("Laptop", 15000)
cart.add("Mouse", 500)
print(f"Normal: {cart.total()} TL") # 15500 TL
cart.set_pricing(PercentageDiscount(10))
print(f"%10 indirim: {cart.total()} TL") # 13950.0 TL
cart.set_pricing(FixedDiscount(2000))
print(f"2000 TL indirim: {cart.total()} TL") # 13500 TL
cart.set_pricing(BuyOneGetOneFree())
print(f"1 al 1 öde: {cart.total()} TL") # 7750.0 TLStrateji nesnesi runtime'da değiştirilebilir — bu kalıtımla mümkün olmayan bir esneklik.
2. Template Method Pattern
Parent'ta algoritmanın iskeletini tanımla, detayları child'lara bırak:
class DataExporter:
"""Template method pattern — export algoritması sabit, format değişken."""
def export(self, data):
"""Algoritma iskeleti — değişmez."""
processed = self._process(data)
formatted = self._format(processed)
output = self._write(formatted)
return output
def _process(self, data):
"""Veriyi temizle — ortak."""
return [str(item).strip() for item in data]
def _format(self, data):
"""Alt sınıf implement eder."""
raise NotImplementedError
def _write(self, formatted):
"""Alt sınıf implement eder."""
raise NotImplementedError
class CSVExporter(DataExporter):
def _format(self, data):
return ",".join(data)
def _write(self, formatted):
return f"CSV: {formatted}"
class JSONExporter(DataExporter):
def _format(self, data):
import json
return json.dumps(data)
def _write(self, formatted):
return f"JSON: {formatted}"
class XMLExporter(DataExporter):
def _format(self, data):
items = "".join(f"<item>{d}</item>" for d in data)
return f"<data>{items}</data>"
def _write(self, formatted):
return f"XML: {formatted}"
data = ["Ali", " Veli ", "Ayşe"]
for exporter in [CSVExporter(), JSONExporter(), XMLExporter()]:
print(exporter.export(data))
# CSV: Ali,Veli,Ayşe
# JSON: ["Ali", "Veli", "Ay\u015fe"]
# XML: <data><item>Ali</item><item>Veli</item><item>Ayşe</item></data>3. Fonksiyonlarla Polimorfizm
Python'da polimorfizm için kalıtım bile gerekli değil. Fonksiyonlar da polimorfik olabilir:
def calculate_area(shape):
"""Her şeklin area() metodu olsun yeter."""
return shape.area()
class Circle:
def __init__(self, r):
self.r = r
def area(self):
import math
return math.pi * self.r ** 2
class Rectangle:
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
class Triangle:
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
# Kalıtım yok! Ama hepsi area() metoduna sahip.
shapes = [Circle(5), Rectangle(4, 6), Triangle(3, 8)]
total = sum(calculate_area(s) for s in shapes)
print(f"Toplam alan: {total:.2f}") # Toplam alan: 114.54Pratik: Şekil Sınıfları — Alan ve Çevre Hesaplama
Kapsamlı bir polimorfizm örneği:
import math
class Shape:
"""Temel şekil sınıfı."""
def area(self):
raise NotImplementedError("Alt sınıf area() implement etmeli!")
def perimeter(self):
raise NotImplementedError("Alt sınıf perimeter() implement etmeli!")
def describe(self):
return (
f"{self.__class__.__name__}: "
f"Alan={self.area():.2f}, "
f"Çevre={self.perimeter():.2f}"
)
def __str__(self):
return self.describe()
def __lt__(self, other):
"""Alana göre sıralama."""
return self.area() < other.area()
def __eq__(self, other):
return self.area() == other.area()
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Triangle(Shape):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def area(self):
# Heron formülü
s = self.perimeter() / 2
return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
def perimeter(self):
return self.a + self.b + self.c
class Square(Rectangle):
"""Kare, eşit kenarlı dikdörtgendir."""
def __init__(self, side):
super().__init__(side, side)
class Ellipse(Shape):
def __init__(self, a, b):
self.a = a # Yarı büyük eksen
self.b = b # Yarı küçük eksen
def area(self):
return math.pi * self.a * self.b
def perimeter(self):
# Ramanujan yaklaşımı
h = ((self.a - self.b) ** 2) / ((self.a + self.b) ** 2)
return math.pi * (self.a + self.b) * (1 + (3 * h) / (10 + math.sqrt(4 - 3 * h)))# Polimorfik kullanım
shapes = [
Circle(5),
Rectangle(4, 6),
Triangle(3, 4, 5),
Square(7),
Ellipse(6, 4),
]
print("=" * 55)
print("ŞEKİL RAPORU")
print("=" * 55)
total_area = 0
for shape in shapes:
print(shape.describe())
total_area += shape.area()
print(f"\nToplam alan: {total_area:.2f}")
# __lt__ sayesinde sıralama çalışır
print("\nAlana göre sıralı:")
for shape in sorted(shapes):
print(f" {shape.__class__.__name__}: {shape.area():.2f}")
# En büyük ve en küçük
largest = max(shapes)
smallest = min(shapes)
print(f"\nEn büyük: {largest.__class__.__name__} ({largest.area():.2f})")
print(f"En küçük: {smallest.__class__.__name__} ({smallest.area():.2f})")Çıktı:
=======================================================
ŞEKİL RAPORU
=======================================================
Circle: Alan=78.54, Çevre=31.42
Rectangle: Alan=24.00, Çevre=20.00
Triangle: Alan=6.00, Çevre=12.00
Square: Alan=49.00, Çevre=28.00
Ellipse: Alan=75.40, Çevre=32.42
Toplam alan: 232.94
Alana göre sıralı:
Triangle: 6.00
Rectangle: 24.00
Square: 49.00
Ellipse: 75.40
Circle: 78.54
En büyük: Circle (78.54)
En küçük: Triangle (6.00)Bu örnekte polimorfizm birden fazla yerde çalışıyor:
describe()her şekil içinarea()veperimeter()çağırıyor — her şeklin kendi versiyonu.sorted()__lt__üzerinden karşılaştırma yapıyor — polimorfik.sum()her şeklinarea()dönüşünü topluyor.
⚠️ Dikkat: Polimorfizm "her sınıfta aynı isimde metod olsun" demek değildir. Anlamlı bir arayüz sözleşmesi olmalı.
area()metodu her zaman sayısal bir alan değeri döndürmeli — bir sınıfta alan döndürüp diğerinde string döndürmek polimorfizm değil, kaos.
Polimorfizm ve SOLID Prensipleri
Polimorfizm, SOLID prensiplerinin özellikle şu ikisiyle doğrudan ilişkili:
Liskov Substitution Principle (LSP)
Alt sınıf, üst sınıfın yerine kullanılabilmelidir. Yani Shape bekleyen her yerde Circle, Rectangle vb. sorunsuz çalışmalı:
def total_area(shapes: list) -> float:
"""Shape bekler — herhangi bir alt sınıf çalışmalı."""
return sum(s.area() for s in shapes)
# Square, Rectangle'ın yerine sorunsuz kullanılabilir — LSP ✅
result = total_area([Circle(5), Rectangle(3, 4), Square(6)])
print(f"Toplam: {result:.2f}")Open/Closed Principle (OCP)
Mevcut kodu değiştirmeden yeni davranış ekleyebilmeliyiz:
# Yeni şekil ekle — mevcut kod DEĞİŞMEZ
class Hexagon(Shape):
def __init__(self, side):
self.side = side
def area(self):
return (3 * math.sqrt(3) / 2) * self.side ** 2
def perimeter(self):
return 6 * self.side
# total_area, describe, sorted — hepsi değişmeden Hexagon'ı destekler!Polimorfizm Olmadan Hayat
Polimorfizmin değerini anlamak için, onsuz nasıl görüneceğini düşün:
# ❌ Polimorfizm OLMADAN — if/elif cehennemi
def calculate_area(shape_type, **kwargs):
if shape_type == "circle":
return math.pi * kwargs["radius"] ** 2
elif shape_type == "rectangle":
return kwargs["width"] * kwargs["height"]
elif shape_type == "triangle":
# Heron formülü...
pass
elif shape_type == "square":
return kwargs["side"] ** 2
# Yeni şekil → buraya elif ekle → fonksiyonu değiştir
# 50 şekil olunca bu fonksiyon 200 satır olur!# ✅ Polimorfizm İLE — temiz, genişletilebilir
shapes = [Circle(5), Rectangle(4, 6), Square(7)]
total = sum(s.area() for s in shapes)
# Yeni şekil → yeni sınıf yaz, mevcut koda dokunmaÖzet
Polimorfizm, aynı arayüzün farklı davranışlar sergilemesidir — "aç" düğmesi her cihazda farklı çalışır.
Duck typing: Python tipine değil davranışa bakar. "Ördek gibi yürüyorsa ördektir." Kalıtım bile gerekmez.
Method overriding ile child sınıflar parent'ın davranışını değiştirir — polimorfizmin en yaygın biçimi.
len(),str(),iter()gibi built-in fonksiyonlar magic method protocol'leri sayesinde polimorfik çalışır.Python'da açık interface gerekmez ama
typing.Protocolile opsiyonel olarak tanımlanabilir (structural subtyping).Strategy pattern ile runtime'da davranış değiştirilebilir — polimorfizmin güçlü kullanımı.
Polimorfizm, kodun genişletilebilir ve bakımı kolay olmasını sağlar — yeni tür eklemek mevcut kodu kırmaz (OCP).
AI Asistan
Sorularını yanıtlamaya hazır