Java’da Nesne Yönelimli Programlama (Object Oriented Programming) Nedir?

Duygu Ozdugan
6 min readFeb 2, 2024

--

Merhaba 🙋 bu yazımda beraber Java ile Nesne Yönelimli Programlama kavramını incelemeye çalışacağız.

Nesne Yönelimli Programlama’da amacımız günlük hayatta kullandığımız nesneleri programlama ortamına uyarlayarak karşılaştığımız sorunları çözebilmektir.

Nesne Yönelimli Programlama’nın sağladığı kolaylıklar;

  • Gerçek dünyadaki nesneleri sınıflar aracılığıyla soyutlarız. Kodun sürdürülebilirliği soyutlama sayesinde artar.
  • Oluşturulan sınıflar birbirinden bağımsız oldukları için bilgi gizleme(information hiding) imkanı sunar.
  • DRY (Don’t Repeat Yourself) ilkesinin korunmasına sınıflar arası ilişkilerle yardımcı olur. DRY, kodların tekrarlanmasını ve buna bağlı çıkabilecek karmaşıklıkların önlenmesini sağlayan bir yazılım geliştirme ilkesidir.

Nesne Yönelimli Programlama’ya dair bilmemiz gereken belli başlı temeller vardır. Gelin şimdi onları birlikte inceleyelim.

Nesne Yönelimli Programlama (Object Oriented Programming) Temelleri

1- Sınıf ve Nesne (Class and Object)

Sınıflar, nesnelerin özelliklerini ve davranışlarını içinde barındırır. Sınıfın değişkenleri nesnelerin özelliklerini ifade ederken metotları da nesnelerin davranışlarını ifade eder. Örneğin bir banka müşterisini düşünelim. Sınıfımız “Musteri” olsun. İsim, soyisim, hesap numarası, bakiye gibi değişkenler müşterinin özelliklerini gösterirken para çek, para yatır metotları ise müşterinin davranışlarıdır.

public class Musteri{
// Degiskenler(Özellikler)
String isim;
String soyisim;
String hesapNumarasi;
double bakiye;

// Metotlar (Davranıslar)
public void paraYatir(double miktar) {
if (miktar > 0) {
bakiye += miktar;
System.out.println(miktar + " TL yatırıldı. Yeni bakiye: " + bakiye + " TL");
} else {
System.out.println("Geçersiz miktar. Lütfen pozitif bir değer girin.");
}
}

public void paraCek(double miktar) {
if (miktar > 0 && miktar <= bakiye) {
bakiye -= miktar;
System.out.println(miktar + " TL çekildi. Yeni bakiye: " + bakiye + " TL");
} else {
System.out.println("Geçersiz miktar veya yetersiz bakiye.");
}
}
}

2- Kapsülleme (Encapsulation)

Kapsülleme, OOP’nin temel prensiplerinden biridir ve sınıf içindeki değişkenlerin ve metotların bilinçli bir şekilde gizlenmesini ifade eder. Tanımladığımız her sınıfta eğer gerçekten geçerli bir sebebimiz yoksa değişkenlerimizi mutlaka doğrudan erişime kapalı bir şekilde yazmamız gerekir.

Neden kapsülleme gereklidir?

Kapsülleme, sınıfın iç işleyişine hakim olmayan kullanıcıların sınıfın detaylarına müdahale etmesini önler. Bu, sınıfın güvenliğini artırır çünkü sınıfın iç detaylarına erişim kontrol altındadır.

Ayrıca kapsülleme sayesinde, sınıfın iç detaylarına hakim olmayan kullanıcılar sadece sınıfı tasarlayan kişinin açıkladığı bir biçimde sınıf ile etkileşimde bulunabilir. Böylece kod daha okunabilir ve erişebilir olur. Peki bu işlemi nasıl gerçekleriz biraz da buna bakalım.

Kapsülleme işlemini Java’da erişim belirteçleri (access modifiers) ile sağlayabiliriz. Toplam 4 adet erişim belirtecimiz bulunuyor.

  1. public: Bu belirteç, ilgili ögenin herkesin erişimine açık olduğunu gösterir. Bu ögeye başka sınıf ve paketlerden erişilebilir.
  2. private: Bu belirteç, ilgili ögenin sadece tanımlandığı sınıf içinden erişilebilir olduğunu gösterir. Diğer sınıf ve paketler bu ögeye doğrudan erişemez.
  3. protected: Bu belirteç, public ile private arasında bir konumda bulunur. Yani aynı sınıf ve paketten bu ögelere erişilebilir ancak farklı bir paketten erişilemez. Dikkat edilmesi gereken nokta ise alt classlar aynı pakette olmasa da ana classa erişebilirler..
  4. default (package-private): Eğer biz ögelerimize herhangi bir belirteç atamamışsak ögemiz default olarak kabul edilir yani aynı sınıf ve paketten erişilebilirken farklı paketten erişim sağlanamaz.
Java’da access modifiers
Java’da erişim belirteçleri

İlk yazdığımız Musteri sınıfımıza geri dönelim. Bu sınıfta hesapNumarasi ve bakiye gibi alanların herkese açık olması bir sürü riski beraberinde getirir. Bu bilgiler müşteriye özel ve gizlenmesi gereken bilgilerdir. Şimdi onları gizleyecek şekilde sınıfımızın değişkenlerini yeniden yazalım.

public class Musteri {

private String ad;
private String soyad;
private String hesapNumarasi;
private double bakiye;

public void paraYatir(double miktar) {
if (miktar > 0) {
bakiye += miktar;
System.out.println(miktar + " TL yatırıldı. Yeni bakiye: " + bakiye + " TL");
} else {
System.out.println("Geçersiz miktar. Lütfen pozitif bir değer girin.");
}
}

public void paraCek(double miktar) {
if (miktar > 0 && miktar <= bakiye) {
bakiye -= miktar;
System.out.println(miktar + " TL çekildi. Yeni bakiye: " + bakiye + " TL");
} else {
System.out.println("Geçersiz miktar veya yetersiz bakiye.");
}
}

Belki de aklınıza şöyle bir soru takılmış olabilir “Peki bu gizlenmiş bilgilere başka sınıf veya paketlerden erişmemiz gerekirse?” Bu gayet anlaşılabilir bir sorudur çünkü gerçekten de bu alanlara ulaşmamız gerekebilir. OOP’de prensip olarak değişkenlerimizi doğrudan erişime kapalı yazdığımız için diğer sınıfların bu değişken veya metotlara erişimi için de bir yol olmalı öyle değil mi? İşte burada constructor ve getter-setter metotlar devreye girecek.

Constructor(Yapıcı Metot) Nedir?

Constructor bir sınıftan nesne oluşturulduğun çağırılan ilk metottur. Bir yazılımcı olarak özellikle bir constructor yazmasak bile Java arka planda bizim için parametresiz bir contructor metot yazar. Parametreli constructorları ise bizim belirtmemiz gerekir. Constructor, nesne oluşturulduğunda çalıştığı için içine geçeceğimiz parametreleri de başlatmış olur.

//Parametresiz Constructor
public Musteri(){

}
//Parametreli Constructor
public Musteri(String ad, String soyad, String hesapNumarasi, double baslangicBakiye) {
this.ad = ad;
this.soyad = soyad;
this.hesapNumarasi = hesapNumarasi;
this.bakiye = baslangicBakiye;
}

Burada kullanılan this anahtar kelimesi ise bir sınıftaki değişkenlere ve metotlara referans sağlamak için kullanılır. Constructor’da kullanılması ise parametre ve değişkenleri ayırmak içindir.

Eşitliğin sağ tarafındaki this.ad, ad parametresini belirtmek için kullanılırken sol taraftaki ad parametre olarak verilen ad değişkenini temsil eder.

Getter-Setter Metotlar Ne İçin Kullanılır?

Sınıflardaki private değişkenlerin içeriklerine erişebilmek ve değer atamak için kullanılan metotlardır. Bu metotlar sınıfın iç detaylarına doğrudan erişim kısıtlanırken değişkenlere güvenli bir şekilde erişilmesine olanak tanır.

Getter Metotlar: Sınıflardaki private değişkenlerin değerlerini almak için kullanılır.

Setter Metotlar: Sınıflardaki private değişkenlere değer atamak veya değerlerini değiştirmek için kullanılır. Setter metotlarıyla değişkenlere değer atanıp değiştirilebileceği için yeni girilecek değerler mutlaka kontrol edilmelidir. Kontrol esnasında uyulması gereken kurallar setter metotlarda yazılır.

 // Getter ve Setter Metotları
public String getAd() {
return ad;
}

public void setAd(String ad) {
this.ad = ad;
}

public String getSoyad() {
return soyad;
}

public void setSoyad(String soyad) {
this.soyad = soyad;
}

public String getHesapNumarasi() {
return hesapNumarasi;
}

public void setHesapNumarasi(String hesapNumarasi) {
this.hesapNumarasi = hesapNumarasi;
}

public double getBakiye() {
return bakiye;
}

public void setBakiye(double bakiye) {
this.bakiye = bakiye;
}

3- Soyutlama (Abstraction)

Soyutlama OOP’nin önemli temellerinden biridir. Gereksiz detayları göz ardı ederek amaca odaklanmayı sağlar. Abstract sınıflar veya metotlarda temel görevler tanımlı iken detaylara yer verilmez. Herhangi bir sorunun çözümünü ilk etapta daha basit ve soyut bir biçimde ele alır.

Bir kütüphanede kitapların ödünç verilme süresi senaryosunu düşünelim. Örneğin, kütüphane görevlileri için herhangi bir kitabın ödünç alma süresi 15 gün, akademisyenler için 30 gün, öğrenciler için ise 7 gün olarak tanımlanmış olsun. Bu senaryoda temel problem, kitapların ödünç alınma süresidir.

Bu soyutlamayı kullanarak, kütüphane sisteminin temel işlevselliğine odaklanabiliriz. Örneğin, her kullanıcı türü için farklı ödünç alma süreleri gibi temel kavramları soyut sınıflar veya arabirimler aracılığıyla belirleyebiliriz.

public abstract class Kullanici {
public abstract int oduncAlmaSuresi();
}

public class KutuphaneGorevlisi extends Kullanici {
@Override
public int oduncAlmaSuresi() {
return 15;
}
}

public class Akademisyen extends Kullanici {
@Override
public int oduncAlmaSuresi() {
return 30;
}
}

public class Ogrenci extends Kullanici {
@Override
public int oduncAlmaSuresi() {
return 7;
}

4- Kalıtım (Inheritance)

Kalıtım, alt sınıfların üst sınıfın özelliklerini ve davranışlarını miras almasıdır. Bir takım sınıflar benzer özellikleri taşıyorsa bunu her sınıf için tekrar tekrar yazmaktansa bir üst sınıfta bu özellikleri toplamak kodun okunabilirliğini ve yeniden kullanılabilirliğini artırır.

Bu örnekte Kitap üst sınıfı temsil ederken Roman, Dergi ve Ansiklopedi alt sınıflardır.


public class Kitap {

private String baslık;
private String yazar;
private int sayfaSayisi;

public Kitap(String baslık, String yazar, int sayfaSayisi) {
this.baslık = baslık;
this.yazar = yazar;
this.sayfaSayisi = sayfaSayisi;
}
}

class Roman extends Kitap{

public Roman(String baslık, String yazar, int sayfaSayisi) {
super(baslık, yazar, sayfaSayisi);
}
}

class Dergi extends Kitap{

public Dergi(String baslık, String yazar, int sayfaSayisi) {
super(baslık, yazar, sayfaSayisi);
}
}

class Ansiklopedi extends Kitap{

public Ansiklopedi(String baslık, String yazar, int sayfaSayisi) {
super(baslık, yazar, sayfaSayisi);
}
}

super() metodu ile bir üst sınıfın constructorunu çağırırız.

5- Çok biçimlilik (Polymorphism)

Çok biçimlilik, Nesne Yönelimli Programlama’nın (OOP) önemli bir konseptidir ve özellikle kalıtım ile yakından ilişkilidir. Bu kavram, üst sınıftan miras alınan metotların farklı şekilde yeniden ele alınabilmesine olanak tanır. Bu, kodu daha esnek ve genişletilebilir hale getirir.

Kodun esnek olması farklı nesne türleriyle daha rahat çalışabilme imkanı sunar. Kodun genişletilebilir olması ise mevcut kodu değiştirmeden yeni özellikler eklemenin bir yolunu sağlar.

Bu örnekte Hayvan üst sınıfında var olan ses çıkartma işlemi her bir alt sınıfta farklı bir şekilde gerçekleşecektir.

public class Hayvan {
public void sesVer(){
System.out.println("Hayvan ses çıkartıyor.");
}
}

class Kedi extends Hayvan{
@Override
public void sesVer() {
System.out.println("Miyav!");
}
}

class Kus extends Hayvan{
@Override
public void sesVer() {
System.out.println("Cik Cik Cik!");
}
}

class Kopek extends Hayvan{
@Override
public void sesVer() {
System.out.println("Hav hav hav!");
}
}

Buraya kadar okuduğunuz için teşekkür ederim 🙏

Eksik ve hatalı yerleri yorumlarda bana bildirerek gelişme sürecime katkıda bulunabilirsiniz 🌸

Beni Linkedin’de takip etmeyi unutmayın.

Yeni yazılarda görüşmek üzere. 👋🏼

--

--