Nesne Yönelimli Programlama (OOP) Nedir? Kod Örnekleriyle 4 Temel Prensip

Nesne yönelimli programlama, yazılımı veri ile o veri üzerinde çalışan davranışları bir araya getiren nesneler üzerinden kurma yaklaşımıdır. 2012'den beri web projeleri yönetiyorum ve ekiplerin kodu büyüdükçe dağılmasını en çok bu düşünce biçimi engelliyor. Bu yazıda dört temel prensibi, yani kapsülleme, kalıtım, çok biçimlilik ve soyutlamayı Python kod örnekleriyle anlatıyorum.
Örneklerde Python kullanıyorum, çünkü sözdizimi sade ve yeni başlayan biri bile kodu rahat okuyabilir. Ancak prensipler Java, C#, PHP veya TypeScript için de aynen geçerlidir. SOLID prensiplerine bu yazıda girmiyorum; o konu ayrı bir yazının işi. Yazılım yazılarının tamamını yazılım kategorisinde bulabilirsiniz.
Nesne yönelimli programlama (OOP) nedir?
Nesne yönelimli programlama, programı birbirleriyle mesajlaşan nesnelerden oluşan bir sistem olarak tasarlayan programlama paradigmasıdır. Her nesne kendi verisini, yani durumunu, ve bu veriyi değiştiren metotları taşır. Böylece kodu gerçek dünyadaki kavramlara benzeyen, küçük ve sorumluluğu belli parçalara ayırırsınız.
Bir e-ticaret sitesini düşünün. Sepet, ürün, müşteri ve sipariş ayrı kavramlardır. OOP bu kavramların her birini bir sınıf olarak tanımlamanızı ister. Sepet sınıfı ürün eklemeyi ve toplamı hesaplamayı bilir; sipariş sınıfı ise durumunu takip eder. Dolayısıyla bir hata çıktığında nereye bakacağınızı bilirsiniz.
Paradigmanın kökleri 1960'lara, Simula diline uzanıyor. Ardından Smalltalk fikri olgunlaştırdı, C++ ve Java da onu ana akıma taşıdı. Bugün Python, PHP, C#, Kotlin ve Swift gibi dillerin hepsi sınıf ve nesne kavramlarını destekliyor. Yani öğrendiğiniz prensipler dil değiştirdiğinizde çöpe gitmiyor.
Sınıf ile nesne arasındaki fark nedir?
Sınıf bir kalıptır, nesne ise o kalıptan üretilen somut örnektir. Mimari bir çizimi düşünün: çizim tek başına bir ev değildir, ama aynı çizimden yüz ev inşa edebilirsiniz. Sınıf da hangi verilerin ve hangi davranışların olacağını tarif eder; nesne bu tarifin bellekteki gerçek hâlidir.
class Urun:
def __init__(self, ad, fiyat):
self.ad = ad
self.fiyat = fiyat
def indirimli_fiyat(self, oran):
return self.fiyat * (1 - oran)
kalem = Urun("Kalem", 40)
defter = Urun("Defter", 120)
print(defter.indirimli_fiyat(0.25)) # 90.0
Burada Urun sınıftır; kalem ve defter ise ondan türeyen iki ayrı nesnedir. İkisi aynı metotları paylaşır, ancak her birinin kendi adı ve fiyatı vardır. __init__ metodu nesne oluşurken çalışan kurucudur. Ayrıca self parametresi, metodun hangi nesne üzerinde çalıştığını gösterir.
İndirim hesabını elle denemek isterseniz indirim hesaplama aracı aynı formülü kullanıyor. Kodla aracın sonucunu karşılaştırmak, ilk sınıfınızı test etmenin eğlenceli bir yoludur.
Nesne yönelimli programlama neden ortaya çıktı?
Prosedürel programlamada veri ve fonksiyonlar ayrı durur. Küçük bir betikte bu sorun değildir. Fakat proje büyüdükçe yüzlerce fonksiyon aynı global veriyi değiştirmeye başlar ve bir değişikliğin nereyi etkilediğini kestirmek zorlaşır. OOP bu karmaşayı veriyi sahibinin yanına koyarak azaltmayı hedefler.
| Kriter | Prosedürel yaklaşım | Nesne yönelimli yaklaşım |
|---|---|---|
| Temel birim | Fonksiyon | Sınıf ve nesne |
| Veri nerede durur? | Çoğunlukla ortak veya global | Sahibi olan nesnenin içinde |
| Yeniden kullanım | Fonksiyon kopyalama veya kütüphane | Kalıtım ve bileşim |
| Değişikliğin etkisi | Geniş alana yayılabilir | Sınıf sınırında kalma eğiliminde |
| Uygun olduğu iş | Kısa betik, tek seferlik işlem | Uzun ömürlü, çok kişili proje |
Bu tablo prosedürel yaklaşımın kötü olduğunu söylemiyor. Örneğin bir CSV dosyasını tek seferde temizleyen yirmi satırlık bir betik için sınıf yazmak gereksiz törendir. Öte yandan yıllarca bakımı sürecek bir yönetim paneli için nesne yapısı ciddi zaman kazandırır.
Benim gözlemim şu: bir projenin nesne yapısına ihtiyaç duyduğunu genellikle üçüncü geliştirici ekibe katıldığında anlarsınız. İlk iki kişi kodu kafasında tutabilir. Ancak yeni gelen biri hangi fonksiyonun hangi veriyi değiştirdiğini çözmeye çalışırken günler kaybeder. Sınıflar bu bilgiyi kodun kendisine yazar ve ekibin ortak dili olur.
Kapsülleme nedir ve hangi sorunu çözer?
Kapsülleme, bir nesnenin verisini dış dünyadan saklayıp o veriye yalnızca kontrollü metotlarla erişilmesini sağlamaktır. Amaç veriyi gizlemek için gizlemek değildir. Asıl amaç, nesnenin her zaman geçerli bir durumda kalmasını garanti etmektir.
Bir banka hesabını düşünün. Bakiyeyi herkes doğrudan değiştirebilseydi, biri eksi beş bin lira yazabilir ve sistem bunu fark etmezdi. Kapsülleme bu kapıyı kapatır: bakiyeyi yalnızca para yatırma ve çekme metotları değiştirir. Bu metotlar da kuralları kontrol eder.
Pratikte kapsülleme size üç şey kazandırır:
- Nesnenin iç yapısını değiştirseniz bile onu kullanan kod bozulmaz.
- Geçersiz veriyi tek bir noktada durdurursunuz, her yerde ayrı ayrı kontrol yazmanız gerekmez.
- Hata ayıklarken verinin hangi yoldan değiştiğini hızla bulursunuz.
Kısacası kapsülleme, nesneye kendi kurallarının bekçiliğini yaptırır. Bunu bir kez alışkanlık hâline getirdiğinizde kodunuzdaki "bu değer nasıl eksiye düştü" türü sürprizler belirgin şekilde azalır.
Kapsülleme kod örneğinde nasıl görünür?
Aşağıdaki sınıf bakiyeyi alt çizgiyle başlayan bir alanda tutuyor ve dışarıya yalnızca okunabilir bir özellik açıyor:
class BankaHesabi:
def __init__(self, sahip):
self.sahip = sahip
self._bakiye = 0
@property
def bakiye(self):
return self._bakiye
def yatir(self, tutar):
if tutar <= 0:
raise ValueError("Tutar pozitif olmalı")
self._bakiye += tutar
def cek(self, tutar):
if tutar > self._bakiye:
raise ValueError("Yetersiz bakiye")
self._bakiye -= tutar
hesap = BankaHesabi("Ayşe")
hesap.yatir(500)
hesap.cek(200)
print(hesap.bakiye) # 300
Bu kodda bakiye özelliğini okuyabilirsiniz, ancak hesap.bakiye = 1000 yazarsanız Python hata verir; çünkü özelliğin yazma metodu yok. Bakiyeyi değiştirmenin tek yolu yatir ve cek metotlarıdır. Böylece "negatif tutar" veya "bakiyeden fazla çekim" gibi kurallar tek yerde yaşar.
Yarın bakiyeyi veritabanından okumaya karar verirseniz yalnızca sınıfın içini değiştirirsiniz. Hesabı kullanan diğer kodlar aynı şekilde hesap.bakiye yazmaya devam eder. İşte kapsüllemenin en somut faydası budur.
Aynı mantığı stok, kupon veya üyelik puanı gibi alanlara da uygulayabilirsiniz. Örneğin bir kupon sınıfı, son kullanma tarihini ve kullanım sayısını içeride tutar ve geçersiz bir kuponun sepete uygulanmasına izin vermez. Kural sınıfın içinde yaşadığı için sepet, ödeme ve yönetim paneli aynı kontrolü paylaşır.
Python'da gerçekten gizli (private) alan var mı?
Hayır, Python'da Java'daki private anahtar kelimesinin birebir karşılığı yok. Python resmi eğitim belgesi de bunu açıkça söylüyor: yalnızca nesnenin içinden erişilebilen gizli değişkenler Python'da bulunmuyor. Bunun yerine bir isimlendirme sözleşmesi kullanırsınız.
Tek alt çizgiyle başlayan bir isim, örneğin _bakiye, "bu iç ayrıntıdır, dışarıdan dokunmayın" mesajı verir. Çift alt çizgiyle başlayan isimlerde ise Python isim karıştırma (name mangling) uygular ve alanın adını sınıf adıyla birleştirir. Bu mekanizma alt sınıflardaki isim çakışmalarını önlemek için vardır, güvenlik için değil.
class Ornek:
def __init__(self):
self.__gizli = 42
o = Ornek()
print(o._Ornek__gizli) # 42, yine de erişilebilir
Dolayısıyla Python'da kapsülleme bir disiplin meselesidir. Takım içinde alt çizgi sözleşmesine uyarsınız, ekip de bu alanlara dışarıdan dokunmaz. Java veya C# gibi dillerde ise derleyici bu kuralı zorla uygular. İki yaklaşımın da amacı aynıdır: nesnenin iç durumunu koruyan net bir sınır çizmek.
Kalıtım nedir ve ne zaman kullanmalısınız?
Kalıtım, bir sınıfın başka bir sınıfın özelliklerini ve metotlarını devralmasıdır. Devralan sınıfa alt sınıf, devredilen sınıfa üst sınıf denir. Alt sınıf ortak davranışı yeniden yazmaz; yalnızca kendine özgü olanı ekler veya değiştirir.
Kalıtımı doğru kullanmanın basit bir testi var: iki kavram arasında "bir türüdür" ilişkisi kurabiliyor musunuz? Kedi bir hayvan türüdür, bu yüzden Kedi sınıfının Hayvan sınıfından türemesi mantıklıdır. Ancak Araba bir Motor türü değildir; arabanın bir motoru vardır. Bu ikinci durumda kalıtım yanlış araçtır.
Kalıtımın uygun olduğu tipik durumlar şunlar:
- Birden fazla sınıf aynı alanları ve davranışların büyük kısmını paylaşıyorsa.
- Bir çerçeve, örneğin Django, sizden kendi sınıfından türetmenizi bekliyorsa.
- Alt sınıflar üst sınıfın sözünü bozmadan onu genişletiyorsa.
Öte yandan kalıtım zinciri üç dört katmanı geçtiğinde kodu okumak zorlaşır. Bir metodun hangi sınıftan geldiğini bulmak için beş dosya açmak zorunda kalırsınız. Bu yüzden ben ekiplerime sığ kalıtım hiyerarşisi öneririm.
Kalıtım kod örneğiyle nasıl çalışır?
Bir web sitesinin kullanıcı sistemini düşünelim. Her kullanıcının adı ve e-posta adresi var; yöneticinin ise ek olarak yetki listesi bulunuyor:
class Kullanici:
def __init__(self, ad, eposta):
self.ad = ad
self.eposta = eposta
def tanit(self):
return f"{self.ad} ({self.eposta})"
def yetkili_mi(self, islem):
return False
class Yonetici(Kullanici):
def __init__(self, ad, eposta, yetkiler):
super().__init__(ad, eposta)
self.yetkiler = set(yetkiler)
def yetkili_mi(self, islem):
return islem in self.yetkiler
y = Yonetici("Mert", "mert@ornek.com", ["sil", "duzenle"])
print(y.tanit()) # devralınan metot
print(y.yetkili_mi("sil")) # True
Yonetici, tanit metodunu hiç yazmadan kullanıyor, çünkü onu üst sınıftan devraldı. super().__init__ çağrısı üst sınıfın kurucusunu çalıştırır; böylece ad ve e-posta atamasını tekrar yazmazsınız. yetkili_mi metodunu ise yeniden tanımladınız. Buna metodu ezme, yani override denir ve bir sonraki prensibin kapısını açar.
Güçlü bir şifre politikası da bu tür sınıfların doğal parçasıdır. Test kullanıcıları için şifre oluşturucu ile rastgele değerler üretebilirsiniz.
Kalıtım yerine bileşim ne zaman daha iyi bir seçimdir?
Bileşim, bir nesnenin başka nesneleri alan olarak taşıması ve işin bir kısmını onlara devretmesidir. "Bir türüdür" yerine "sahiptir" ilişkisini kurar. Tasarım desenleri literatüründe sık tekrar edilen öneri de budur: mümkün olduğunda kalıtım yerine bileşimi tercih edin.
class Motor:
def calistir(self):
return "Motor çalıştı"
class Araba:
def __init__(self, motor):
self.motor = motor
def yola_cik(self):
return self.motor.calistir() + ", araba hareket ediyor"
araba = Araba(Motor())
Bu yapıda yarın elektrikli bir motor sınıfı yazıp arabaya verebilirsiniz; Araba sınıfında tek satır değişmez. Kalıtımla aynı esnekliği kurmak için her motor tipine ayrı bir araba alt sınıfı yazmanız gerekirdi.
Benim pratik kuralım şu: ilişkiyi yüksek sesle söylüyorum. "Yönetici bir kullanıcıdır" kulağa doğru geliyorsa kalıtım kullanırım. Ancak "fatura bir PDF'tir" kulağa tuhaf geliyorsa, faturaya bir PDF oluşturucu nesnesi veririm. Bu küçük alışkanlık, ileride kırılgan hiyerarşileri söküp atma işinden sizi kurtarır.
Çok biçimlilik (polimorfizm) nedir?
Çok biçimlilik, farklı sınıflardan nesnelerin aynı metot çağrısına kendi yöntemleriyle cevap verebilmesidir. Kelime Yunancada "çok biçim" anlamına gelir. Çağıran kod nesnenin tam türünü bilmek zorunda kalmaz; yalnızca beklediği metodun var olduğunu bilir.
Gündelik bir örnek verelim. "Çal" komutunu bir gitara, bir piyanoya ve bir davula verdiğinizde üçü de ses çıkarır, ama her biri farklı şekilde. Komutu veren kişi her enstrümanın içini bilmez. Yazılımda da ödeme, bildirim veya dışa aktarma gibi işlerde aynı durumla sık karşılaşırsınız.
Çok biçimliliğin iki yaygın biçimi var. İlki, alt sınıfların üst sınıf metodunu ezmesiyle ortaya çıkan çalışma zamanı çok biçimliliğidir. İkincisi, Java ve C# gibi dillerde aynı isimli metodun farklı parametrelerle tanımlanmasıdır; buna aşırı yükleme (overloading) denir. Python aşırı yüklemeyi doğrudan desteklemez, bunun yerine varsayılan parametreler kullanırsınız.
Asıl kazanç, yeni bir tür eklediğinizde mevcut kodu değiştirmemenizdir. Böylece uzun if ve elif zincirleri yerini küçük, bağımsız sınıflara bırakır.
Çok biçimliliği kodda nasıl uygularsınız?
Bir e-ticaret sitesinin ödeme adımını ele alalım. Kredi kartı, havale ve kapıda ödeme farklı işler yapar, ama sipariş kodu hepsine aynı şekilde seslenmek ister:
class KrediKarti:
def ode(self, tutar):
return f"{tutar} TL karttan tahsil edildi"
class Havale:
def ode(self, tutar):
return f"{tutar} TL için IBAN bilgisi gönderildi"
class KapidaOdeme:
def ode(self, tutar):
return f"{tutar} TL teslimatta alınacak"
def siparisi_tamamla(odeme_yontemi, tutar):
print(odeme_yontemi.ode(tutar))
for yontem in [KrediKarti(), Havale(), KapidaOdeme()]:
siparisi_tamamla(yontem, 750)
siparisi_tamamla fonksiyonu hangi ödeme türüyle çalıştığını sormuyor. Yalnızca ode metodunu çağırıyor ve her nesne kendi cevabını veriyor. Yarın yeni bir cüzdan ödemesi eklerseniz, yeni bir sınıf yazarsınız ve sipariş koduna hiç dokunmazsınız.
Ödeme akışlarını tasarlarken yazılım mimarisi kadar kullanıcı deneyimi de önemli. Bu konuda e-ticaret danışmanlığı sayfasında ödeme adımında dikkat ettiğim noktaları anlatıyorum.
Duck typing çok biçimliliğin bir türü müdür?
Evet, Python'daki çok biçimliliğin büyük kısmı duck typing üzerinden çalışır. Önceki örnekte üç ödeme sınıfı ortak bir üst sınıftan türemiyordu, ama yine de aynı fonksiyonla çalıştı. Python nesnenin türüne değil, beklenen metodun var olup olmadığına baktı.
Python sözlüğündeki tanım bu fikri özetliyor: bir nesnenin türüne bakmak yerine, doğrudan metodunu çağırır veya özelliğini kullanırsınız. İfadenin kaynağı eski bir deyimdir: ördek gibi yürüyor ve ördek gibi vaklıyorsa, onu ördek kabul edersiniz.
Bu esneklik güçlü, ama bir riski var. Bir sınıfta ode yerine yanlışlıkla odeme yazarsanız hata yalnızca o satır çalıştığında ortaya çıkar. Bu yüzden büyük projelerde iki yöntemden birini kullanırım:
- Tür ipuçları ve typing.Protocol ile beklenen arayüzü yazmak, sonra mypy gibi bir denetleyiciyle kontrol etmek.
- Ortak bir soyut sınıf tanımlamak ve alt sınıfları metodu yazmaya zorlamak.
İkinci yöntem bizi doğrudan dördüncü prensibe, yani soyutlamaya götürüyor.
Soyutlama nedir, kapsüllemeden farkı ne?
Soyutlama, bir nesnenin ne yaptığını öne çıkarıp nasıl yaptığını arka plana itmektir. Kullanıcıya yalnızca işine yarayan arayüzü gösterirsiniz. Araba sürerken direksiyon, gaz ve fren kullanırsınız; yakıt enjeksiyonunun zamanlamasını bilmeniz gerekmez.
Birçok geliştirici iki kavramı sık karıştırır, çünkü ikisi de ayrıntıyı saklıyor gibi görünür. Ancak odakları farklıdır. Soyutlama bir tasarım kararıdır: "bu nesnenin dışarıya hangi yetenekleri sunması gerekir?" sorusuna cevap verir. Kapsülleme ise bir uygulama tekniğidir: "iç durumu nasıl korurum?" sorusunu çözer.
| Soru | Soyutlama | Kapsülleme |
|---|---|---|
| Neye odaklanır? | Nesnenin ne yaptığına | Verinin nasıl korunduğuna |
| Hangi aşamada düşünürsünüz? | Tasarım | Uygulama |
| Python'daki aracı | abc modülü, Protocol | Alt çizgi sözleşmesi, property |
| Kazanç | Karmaşıklığı gizler | Geçersiz durumu engeller |
Kısacası soyutlama "neyi göstereceğim", kapsülleme "neyi koruyacağım" sorusudur. İyi bir sınıf ikisini birlikte uygular.
Soyut sınıfı kod örneğiyle nasıl yazarsınız?
Python'da soyut sınıfları standart kütüphanedeki abc modülüyle yazarsınız. abc modülü belgesine göre soyut metotları ezilmemiş bir sınıftan nesne oluşturamazsınız. Bu kural, alt sınıfları sözleşmeye uymaya zorlar:
from abc import ABC, abstractmethod
class Bildirim(ABC):
def __init__(self, alici):
self.alici = alici
@abstractmethod
def gonder(self, mesaj):
...
def gonder_ve_kaydet(self, mesaj):
sonuc = self.gonder(mesaj)
print(f"Kayıt: {self.alici} / {sonuc}")
class EpostaBildirimi(Bildirim):
def gonder(self, mesaj):
return f"E-posta gitti: {mesaj}"
class SmsBildirimi(Bildirim):
def gonder(self, mesaj):
return f"SMS gitti: {mesaj[:160]}"
Bildirim() yazarak doğrudan nesne üretmeye çalışırsanız Python TypeError verir. Bunun nedeni sınıfın gonder metodunu soyut bırakmasıdır. Öte yandan gonder_ve_kaydet somut bir metottur ve tüm alt sınıflar onu hazır olarak kullanır.
SMS örneğindeki 160 karakter sınırı gibi uzunluk kontrollerini denemek için kelime ve karakter sayacı işinize yarar. Mesaj şablonlarınızı önce orada ölçüp sonra koda taşıyabilirsiniz.
Dört temel prensip tek bir projede nasıl birlikte çalışır?
Prensipleri tek tek öğrenmek kolaydır; asıl beceri onları birlikte kullanmaktır. Az önceki bildirim sistemini düşünün. Dört prensibin hepsi aynı küçük yapıda görev alıyor:
- Soyutlama: Bildirim sınıfı yalnızca "gönder" yeteneğini tarif ediyor, kanal ayrıntısını saklıyor.
- Kalıtım: E-posta ve SMS sınıfları ortak alıcı alanını ve kayıt metodunu devralıyor.
- Çok biçimlilik: Aynı gonder_ve_kaydet çağrısı her kanalda farklı sonuç üretiyor.
- Kapsülleme: Her kanal kendi bağlantı bilgisini içeride tutuyor, dışarıya açmıyor.
kanallar = [EpostaBildirimi("ayse@ornek.com"),
SmsBildirimi("+905000000000")]
for kanal in kanallar:
kanal.gonder_ve_kaydet("Siparişiniz kargoya verildi")
Bu döngü yeni bir WhatsApp kanalı eklediğinizde de aynen çalışır. Yalnızca Bildirim sınıfından türeyen yeni bir sınıf yazarsınız. Böylece mevcut ve test ettiğiniz koda dokunmadan sistemi büyütürsünüz. Büyük sitelerde aynı düşünceyi arayüz katmanına taşıyan yaklaşımı micro frontend yazısında anlatıyorum.
Nesne yönelimli programlamada en sık yapılan hatalar nelerdir?
Yıllar içinde devraldığım projelerde aynı hataları tekrar tekrar gördüm. Çoğunun kökü prensipleri bilmemek değil, onları aşırı veya yanlış yerde uygulamak:
- Tanrı sınıfı: Her şeyi bilen ve binlerce satıra ulaşan tek bir sınıf. Kapsüllemenin tam tersi bir sonuç doğurur.
- Derin kalıtım: Beş altı katmanlı hiyerarşiler. Bir metodun kaynağını bulmak işkenceye döner.
- Anlamsız getter ve setter: Her alana kontrolsüz okuma ve yazma metodu eklemek. Bu kapsülleme değil, yalnızca törendir.
- Tür kontrolü zinciri: Çok biçimlilik yerine isinstance ile uzun if blokları yazmak.
- Erken soyutlama: Tek bir kullanım varken üç katmanlı arayüz kurmak.
Bu hataların ortak noktası, kodun değişime direnmesidir. Üstelik kötü yapı genellikle performansa da yansır; gereksiz nesne üretimi ve tekrar eden sorgular sayfayı yavaşlatır. Sitenizin hızını ölçmek isterseniz Google Lighthouse ile performans testi rehberine bakabilirsiniz.
OOP ile fonksiyonel programlama birbirinin rakibi midir?
Hayır, modern dillerin çoğu iki paradigmayı birlikte destekliyor. Fonksiyonel programlama değişmeyen veri ve yan etkisiz fonksiyonlara odaklanır. Nesne yönelimli programlama ise durumu nesnelerin içinde toplar. İkisi farklı sorunlar için güçlü araçlar sunar.
Örneğin Python'da bir sınıfın içinde map, filter veya liste üreteçleri kullanmak son derece doğaldır. JavaScript ve TypeScript tarafında da React bileşenleri zamanla sınıf yapısından fonksiyon ve hook yapısına geçti. Yine de iş kuralları, veri modelleri ve servis katmanları çoğu projede sınıflarla yazılmaya devam ediyor.
Benim yaklaşımım pragmatik: veri dönüşümlerinde fonksiyonel, alan modellerinde nesne yönelimli düşünürüm. Önemli olan bir paradigmaya körü körüne bağlanmak değil, kodu okuyacak bir sonraki kişiyi düşünmektir. Kısacası iki yaklaşımı da bilmek sizi daha esnek bir geliştirici yapar.
İlginç bir benzetme de web tarafından geliyor. Arama motorlarına sayfanızı anlatan JSON-LD işaretlemesi de aslında türü, özellikleri ve iç içe nesneleri olan bir modeldir. Bir Ürün nesnesinin içinde Marka ve Teklif nesneleri durur. Bu yapıyı merak ediyorsanız schema markup rehberine göz atabilirsiniz; sınıf mantığını bildiğinizde işaretlemeyi okumak çok kolaylaşır.
Nesne yönelimli programlamayı öğrenirken hangi sırayı izlemelisiniz?
Yeni başlayanların en sık yaptığı hata, dört prensibi ezberleyip hiç kod yazmamaktır. Oysa bu kavramlar ancak küçük projelerde elle deneyince oturur. Saha tecrübeme dayanarak önerdiğim sıra şöyle, ama kesin bir müfredat değil:
- Sınıf, nesne, kurucu ve self kavramlarını küçük örneklerle oturtun.
- Bir banka hesabı veya stok sınıfıyla kapsüllemeyi deneyin.
- Kullanıcı ve yönetici gibi iki seviyeli bir hiyerarşiyle kalıtımı uygulayın.
- Aynı hiyerarşiyi bileşimle yeniden yazıp farkı karşılaştırın.
- Ödeme veya bildirim gibi bir senaryoda çok biçimliliği kurun.
- abc modülüyle soyut bir sınıf yazın ve alt sınıfları ona uydurun.
- Son olarak SOLID prensiplerine ve temel tasarım desenlerine geçin.
Her adımda küçük bir test yazmanızı öneririm. Örneğin banka hesabı sınıfında "eksi tutar hata vermeli" kuralını bir testle doğrulamak, kapsüllemenin neden önemli olduğunu size ezberden çok daha iyi öğretir.
Küçük bir alıştırma: slug üreten bir sınıf nasıl yazılır?
Öğrendiklerinizi pekiştirmek için web dünyasından tanıdık bir iş seçelim: başlıktan adres uzantısı, yani slug üretmek. Bu iş kapsülleme için ideal, çünkü dönüşüm kuralları dışarıdan görünmemeli:
class SlugUretici:
_harita = str.maketrans("çğıöşüÇĞİÖŞÜ", "cgiosuCGIOSU")
def __init__(self, ayirici="-"):
self._ayirici = ayirici
def uret(self, baslik):
metin = baslik.translate(self._harita).lower()
kelimeler = [k for k in metin.split() if k.isalnum()]
return self._ayirici.join(kelimeler)
print(SlugUretici().uret("Nesne Yönelimli Programlama Nedir"))
Çıktı nesne-yonelimli-programlama-nedir olur. Kodu genişletmek size kalmış: noktalama işaretlerini temizleyen bir alt sınıf yazabilir veya ayırıcıyı alt çizgi yapabilirsiniz. Sonucu slug oluşturucu aracı ile karşılaştırırsanız hangi kenar durumlarını atladığınızı hemen görürsünüz.
Bu alıştırma aynı zamanda SEO açısından da öğretici. Temiz adres yapısı arama motorlarının sayfayı anlamasına yardım eder; dolayısıyla yazdığınız küçük sınıf gerçek bir web projesinde doğrudan iş görür.
OOP bilgisi bir web projesinde size ne kazandırır?
Kurumsal bir web sitesi veya e-ticaret altyapısı yıllarca yaşar. Bu süre boyunca yeni ödeme yöntemleri, yeni diller ve yeni entegrasyonlar gelir. Nesne yönelimli prensiplere sadık bir kod tabanı bu değişiklikleri küçük, bağımsız sınıflar olarak kabul eder. Özensiz bir yapı ise her değişiklikte başka bir yeri kırar.
Ben web tasarım projelerinde yazılım ekibiyle ilk toplantıda iki şeyi sorarım: iş kuralları nerede duruyor ve yeni bir özellik kaç dosyaya dokunuyor? Bu iki cevap, kodun nesne yönelimli prensiplere ne kadar sadık olduğunu hızlıca gösterir.
Sonuç olarak dört prensip birer kural listesi değil, değişimi ucuzlatan alışkanlıklardır. Kapsülleme veriyi korur, kalıtım tekrarı azaltır, çok biçimlilik yeni türleri kolay ekler, soyutlama ise karmaşıklığı gizler. Bir sonraki adım SOLID prensipleridir ve bu konuyu yazılım kategorisindeki ayrı yazıda ele alıyorum.




