Nesne yönelimli programlama (OOP), bir programı birbiriyle iletişim kuran nesneler etrafında kurma yaklaşımıdır. Her nesne kendi verisini (özellik) ve bu veri üzerinde çalışan işlevlerini (metot) bir arada tutar. Java, C#, Python ve C++ gibi diller bu yaklaşımı destekler; günümüzün büyük yazılım sistemlerinin çoğu OOP ile yazılır.
Bu yazıda OOP'nin dört temel kavramını tek tek, gerçek kod örnekleriyle ve tekrara girmeden anlatıyoruz. Ardından ne zaman OOP kullanmanın anlamlı olduğuna, kompozisyon alternatifine, SOLID ilkelerine ve sık yapılan hatalara bakıyoruz.
Nesne, sınıf ve örnek: temel sözlük
Bir sınıf (class), bir nesnenin nasıl olacağını tanımlayan şablondur. Bir nesne (object/instance) ise bu şablondan üretilmiş somut bir örnektir. "Araba" bir sınıfsa, garajınızdaki gerçek araba bir nesnedir. Python'da bu ilişki şöyle görünür:
class Araba:
def __init__(self, marka, hiz=0):
self.marka = marka
self.hiz = hiz
def hizlan(self, miktar):
self.hiz += miktar
araba1 = Araba("Toyota")
araba1.hizlan(20)
print(araba1.hiz) # 20Araba sınıfı bir şablon; araba1 bu şablondan üretilmiş bir nesne. marka ve hiz özellik (veri), hizlan ise metot (davranış). Aynı sınıftan istediğiniz kadar nesne üretebilirsiniz, her biri kendi verisini taşır.
OOP'nin dört temel kavramı
OOP genelde dört kavram üzerinden anlatılır. Aşağıda her birini bir kez, net bir tanım ve tek bir kod örneğiyle ele alıyoruz.
Kapsülleme (encapsulation)
Kapsülleme, bir nesnenin verisini ve bu veriyi değiştiren metotları bir arada tutup dışarıdan kontrolsüz erişimi engellemektir. Amaç veriyi gizlemek değil, veriye nasıl erişileceğini kontrol etmektir:
class BankaHesabi:
def __init__(self, bakiye):
self._bakiye = bakiye
def para_yatir(self, miktar):
if miktar > 0:
self._bakiye += miktar
def bakiye_goster(self):
return self._bakiyeBakiye doğrudan dışarıdan değiştirilebilseydi (hesap.bakiye = -500 gibi) hatalı durumların önüne geçilemezdi. para_yatir metodu, negatif miktarları reddederek bu kontrolü sağlıyor. Kapsülleme, bir nesnenin iç detaylarını değiştirdiğinizde bu değişikliğin geri kalan koda minimum etkiyle yansımasını da kolaylaştırır.
Kalıtım (inheritance)
Kalıtım, bir sınıfın başka bir sınıfın özelliklerini ve metotlarını devralmasıdır. Amaç, ortak davranışı tekrar tekrar yazmak yerine bir kez tanımlayıp paylaşmaktır:
class Arac:
def __init__(self, marka):
self.marka = marka
def bilgi(self):
return f"{self.marka} bir araçtır"
class Araba(Arac):
def bilgi(self):
return f"{self.marka} bir arabadır"Araba, Arac sınıfının __init__ metodunu devralır ama bilgi metodunu kendi ihtiyacına göre yeniden tanımlar (override). Kod tekrarı azalır: ortak alanlar bir kez, üst sınıfta yazılır.
Çok biçimlilik (polymorphism)
Çok biçimlilik, aynı isimdeki bir metodun farklı sınıflarda farklı davranmasıdır. Yukarıdaki Arac ve Araba sınıflarının ikisinde de bilgi metodu var ama farklı sonuç döndürüyor. Bunun pratik faydası, bir listeyi gezerken hangi nesnenin hangi sınıftan geldiğini bilmenize gerek kalmamasıdır:
araclar = [Arac("Genel"), Araba("Toyota")]
for a in araclar:
print(a.bilgi())
# Genel bir araçtır
# Toyota bir arabadırKod, listedeki her elemanın tam olarak hangi sınıftan olduğunu kontrol etmek zorunda kalmadan bilgi() metodunu çağırıyor; her nesne kendi davranışını sergiliyor. Bu, yeni bir araç türü eklendiğinde mevcut döngü kodunu değiştirmenize gerek kalmaması anlamına gelir.
Soyutlama (abstraction)
Soyutlama, bir nesnenin gereksiz iç detaylarını gizleyip yalnızca dışarıdan gerekli olan davranışı göstermektir. Bir arayüz (interface) veya soyut sınıf, hangi metotların bulunması gerektiğini tanımlar; bu metotların içeride nasıl çalıştığını değil. araba1.hizlan(20) çağrısını yaparken motorun içindeki mekanizmayı bilmenize gerek yok – sadece hizlan metodunun ne yaptığını bilmeniz yeterli. Soyutlama, kapsüllemeyle iç içe çalışır: kapsülleme veriyi korur, soyutlama karmaşıklığı kullanıcıdan gizler.
Kompozisyon: kalıtımın bir alternatifi
Kalıtım her zaman en doğru araç değildir. Bir sınıf, başka bir sınıfı miras almak yerine, o sınıfın bir örneğini kendi içinde taşıyabilir; buna kompozisyon denir. "Araba bir Araçtır" (kalıtım) ile "Araba bir Motora sahiptir" (kompozisyon) arasındaki fark, ilişkinin doğasını doğru modellemekle ilgilidir:
class Motor:
def __init__(self, guc):
self.guc = guc
def calistir(self):
return f"{self.guc} beygirlik motor çalışıyor"
class Araba:
def __init__(self, marka, motor):
self.marka = marka
self.motor = motor # kompozisyon: Araba bir Motora sahip
def baslat(self):
return f"{self.marka}: {self.motor.calistir()}"Burada Araba, Motor sınıfından kalıtım almıyor; onu bir özellik olarak içinde taşıyor. Bu yaklaşım sınıflar arasında daha gevşek bir bağ kurar ve aynı motoru başka bir araca – örneğin bir kamyona – kolayca ekleyebilmenizi sağlar. Yazılım camiasında sık duyulan "kalıtım yerine kompozisyonu tercih edin" önerisi buradan gelir: kalıtım sıkı bir bağ kurar, kompozisyon parçaları daha bağımsız tutar.
Statik Üyeler: Sınıfa mı, Nesneye mi Ait?
Şimdiye kadarki örneklerde her özellik ve metot, o sınıftan üretilen her nesneye ayrı ayrı ait oldu. Ancak bazı veriler nesneye değil, sınıfın kendisine ait olmalıdır — örneğin oluşturulan toplam nesne sayısı. Bunun için statik (static) üye kullanılır:
class Araba:
toplam_araba_sayisi = 0 # sinifa ait, tum nesneler paylasir
def __init__(self, marka):
self.marka = marka # nesneye ait
Araba.toplam_araba_sayisi += 1
araba1 = Araba("Toyota")
araba2 = Araba("Renault")
print(Araba.toplam_araba_sayisi) # 2Statik üyeler, tüm nesneler arasında paylaşılan ortak bir sayaç, sabit veya yardımcı fonksiyon tanımlamak istediğinizde kullanılır; her nesnenin kendi kopyasını tutmasına gerek olmayan veriler için idealdir.
Sınıf mı fonksiyon mu? OOP ne zaman anlamlı
OOP her problem için doğru araç değil. Küçük bir betik (script) ya da tek seferlik bir veri dönüşümü için sınıf tanımlamak, çözümden çok karmaşıklık ekler. OOP'nin değeri, birden fazla nesne türünün birbiriyle etkileşime girdiği, durumun (state) zamanla değiştiği ve birden fazla kişinin aynı kod tabanı üzerinde çalıştığı projelerde ortaya çıkar.
Örneğin, bir CSV dosyasındaki sayıları toplayıp ortalamasını yazdıran bir betik için sınıf tanımlamak gereksiz; birkaç fonksiyon yeterli. Ama bir sipariş yönetim sistemi – müşteri, sipariş, ürün ve ödeme gibi birbiriyle ilişkili birden fazla varlığın bulunduğu bir yapı – için OOP, bu varlıklar arasındaki ilişkiyi ve zamanla değişen durumu yönetmeyi büyük ölçüde kolaylaştırır.
| Durum | OOP mantıklı mı? |
|---|---|
| Tek seferlik veri dönüşüm betiği | Genelde hayır – sade fonksiyonlar yeterli |
| Birden fazla varlık türü olan bir sistem (kullanıcı, sipariş, ürün gibi) | Evet – her varlık kendi verisini ve davranışını taşıyabilir |
| Büyük bir ekibin uzun süre bakımını yapacağı bir kod tabanı | Evet – net sınırlar ve kapsülleme bakımı kolaylaştırır |
| Basit, durumu olmayan bir hesaplama (örn. iki sayıyı toplama) | Hayır – doğrudan bir fonksiyon yeterli |
OOP ile Fonksiyonel Programlama Arasındaki Fark
OOP'nin en sık karşılaştırıldığı yaklaşım, fonksiyonel programlamadır. OOP, durumu (state) nesnelerin içinde tutup bu durumu değiştiren metotlar tanımlarken; fonksiyonel programlama durumu değiştirmemeyi (immutability) ve aynı girdiyle her zaman aynı çıktıyı üreten "saf fonksiyonlar" yazmayı önceler. İkisi birbirinin zıttı değil, farklı problemlere daha uygun iki araçtır: bir sipariş sisteminde müşterinin sepeti zamanla değişir, bu durumu bir nesnede tutmak doğaldır; bir veri dönüşüm hattında (bir listeyi filtrele, dönüştür, topla) fonksiyonel stil genelde daha kısa ve test edilmesi daha kolay kod üretir. Modern diller (Python, JavaScript, C#, Java) artık ikisini bir arada destekler; katı bir "ya OOP ya fonksiyonel" ayrımı yerine, hangi problemin hangi stile daha uygun olduğuna bakmak daha pratik bir yaklaşımdır.
AppFellas
Fikrinizi konuşalım.
Ücretsiz keşif görüşmesinde projenizi, bütçenizi ve zaman çizelgenizi netleştirelim.
SOLID: OOP kodunu dağınıklıktan koruyan 5 ilke
SOLID, büyüyen bir OOP kod tabanının okunabilir ve değiştirilebilir kalması için sık referans verilen beş ilkenin baş harflerinden oluşur. Bunları ezbere kural olarak değil, bir sınıfı değiştirirken "bu değişiklik neden zor oldu" sorusuna cevap arayan bir kontrol listesi gibi düşünmek daha faydalı:
- Tek Sorumluluk: Bir sınıfın değişmesi için tek bir nedeni olmalı; hem veritabanına yazan hem e-posta gönderen bir sınıf, iki farklı sorumluluğu karıştırır.
- Açık/Kapalı: Sınıflar yeni davranış eklemeye açık, mevcut ve test edilmiş koda dokunmaya kapalı olmalı.
- Liskov Yerine Geçme: Bir alt sınıf, üst sınıfın kullanıldığı her yerde sorunsuz onun yerine geçebilmeli;
Araba,Arac'ın beklediği davranışı bozmamalı. - Arayüz Ayrımı: Her şeyi yapan tek bir büyük arayüz yerine, küçük ve amaca özel arayüzler tercih edilmeli.
- Bağımlılıkların Tersine Çevrilmesi: Kod, somut bir uygulamaya değil soyut bir arayüze bağımlı olmalı; böylece o uygulama değiştiğinde geri kalan kod etkilenmez.
Sık yapılan hatalar
- Her şeyi sınıfa sokmak: Basit bir betiği bile gereksiz sınıf hiyerarşileriyle karmaşıklaştırmak, kodu okumayı zorlaştırır.
- Kalıtımı her yerde kullanmak: Bazen "içerir" ilişkisi (kompozisyon), "bir türüdür" ilişkisinden (kalıtım) daha uygun; her ortak özellik kalıtım gerektirmez.
- Kapsüllemeyi atlamak: Tüm özellikleri doğrudan dışarıya açık bırakmak, kontrolsüz değişikliklere ve tutarsız verilere yol açar.
- SOLID'i ezbere uygulamak: Her ilkeyi mekanik şekilde uygulamaya çalışmak, küçük bir projede gereksiz katman ve dosya sayısına yol açabilir.
Hangi diller OOP destekler?
Diller OOP'yi ne kadar zorunlu kıldıklarına göre de ayrışır:
| Dil | OOP desteği |
|---|---|
| Java | Tamamen nesne yönelimli tasarlanmış; her kod bir sınıfın içinde yaşar |
| Python | Nesne yönelimli ama zorunlu değil; fonksiyonel ve prosedürel stiller de kullanılabilir |
| C++ | Hem prosedürel hem nesne yönelimli programlamayı destekler |
| JavaScript | Prototip tabanlı ama <code>class</code> sözdizimiyle OOP tarzı yazılabilir |
Örnek Senaryo: Bir E-Ticaret Uygulamasında OOP
Dört kavramın bir arada nasıl çalıştığını görmek için bir e-ticaret uygulamasını ele alalım. Urun sınıfı ad ve fiyatı kapsüller; IndirimliUrun sınıfı Urun'dan kalıtım alıp fiyat hesaplamasını override ederek çok biçimlilik sağlar; Sepet sınıfı ürünleri kompozisyonla içinde tutar:
class Urun:
def __init__(self, ad, fiyat):
self.ad = ad
self.fiyat = fiyat
def toplam_fiyat(self):
return self.fiyat
class IndirimliUrun(Urun):
def __init__(self, ad, fiyat, indirim_orani):
super().__init__(ad, fiyat)
self.indirim_orani = indirim_orani
def toplam_fiyat(self):
return self.fiyat * (1 - self.indirim_orani)
class Sepet:
def __init__(self):
self.urunler = [] # kompozisyon
def urun_ekle(self, urun):
self.urunler.append(urun)
def toplam(self):
return sum(u.toplam_fiyat() for u in self.urunler) # polimorfizmSepet.toplam() metodu, listedeki her ürünün Urun mu IndirimliUrun mu olduğunu bilmeden toplam_fiyat() çağırır — her nesne kendi hesaplamasını yapar. Yeni bir ürün türü (örneğin KampanyaliUrun) eklendiğinde Sepet sınıfının kodunu değiştirmeye gerek kalmaz; bu, çok biçimliliğin pratikte sağladığı asıl esnekliktir.
OOP'yi öğrenmeye nereden başlamalı?
OOP'ye geçmeden önce seçtiğiniz dilin temel sözdizimini – değişken, döngü, fonksiyon – oturtmuş olmanız gerekir; aksi hâlde sınıf ve nesne kavramları havada kalır. Python'a özel bir zaman çizelgesini Python dili kaç ayda öğrenilir yazımızda, genel süreci yazılım öğrenmek ne kadar sürer yazımızda anlattık.
En iyi pratik yöntemi, gerçek bir şeyi modellemek: bir kütüphane ödünç verme sistemi (Kitap, Üye, Ödünç Alma sınıfları), ya da bir görev listesi uygulamasını nesnelerle yeniden yazmak (her görev kendi durumunu taşıyan bir nesne). Yoğun bir başlangıç planı arıyorsanız 3 ayda yazılım öğrenilir mi yazımızdaki 12 haftalık plana bakabilirsiniz.
Ekibinizin ihtiyaç duyduğu bir sistemi baştan tasarlamak istiyorsanız tamamladığımız projelere göz atabilir ya da bize yazabilirsiniz.
Sıkça Sorulan Sorular
Sık sorulan sorular
Nesne Yönelimli Programlama nedir?
Bir programı, veri ve davranışı birlikte taşıyan nesneler etrafında kurma yaklaşımıdır. Java, C#, Python ve C++ gibi diller bu yaklaşımı destekler.
Sınıf ile nesne arasındaki fark nedir?
Sınıf, bir nesnenin nasıl olacağını tanımlayan şablondur. Nesne, bu şablondan üretilmiş somut bir örnektir. Aynı sınıftan birden fazla nesne üretilebilir, her biri kendi verisini taşır.
Kalıtım neden kullanılır?
Ortak özellik ve davranışları tek bir üst sınıfta tanımlayıp alt sınıflara aktarmak için kullanılır. Bu, aynı kodu birden fazla sınıfta tekrar yazmayı önler.
Kapsülleme ne işe yarar?
Bir nesnenin verisini, o veriyi kontrollü şekilde değiştiren metotların arkasına gizler. Böylece veri, geçersiz veya tutarsız bir duruma doğrudan dışarıdan getirilemez.
Her projede OOP kullanmak gerekir mi?
Hayır. Basit betikler ve durumu olmayan hesaplamalar için sade fonksiyonlar yeterli olur. OOP, birden fazla nesne türünün etkileşime girdiği ve zamanla büyüyen projelerde değer katar.
Python'da OOP kullanmak zorunlu mu?
Hayır. Python fonksiyonel ve prosedürel stilleri de destekler. Küçük bir script için sınıf tanımlamak şart değil; ihtiyaç birden fazla ilişkili veri ve davranışı bir arada tutmaya geldiğinde OOP devreye girer.



