OOP’nin 4 Ana Prensibi: Encapsulation, Inheritence, Abstraction, Polymorphism

10/01/2019

İyi bir kod tasarımı için uyulması gereken bazı prensipler vardır. Prensipler zorunlu değildir fakat uyulduğunda hem dünya çapında standart bir kod yazmış oluruz hem de sürdürülebilirliği yüksek bir iş ortaya çıkar. Aslında herhangi bir programlama dilinde az da olsa bir geçmişiniz varsa bu prensipleri muhtemelen kullanıyorsunuzdur fakat adlarını bilmiyorsunuzdur. Öğrenmesi ve uygulaması çok kolay olan bu prensipler özellikle Junior yazılımcılar tarafından korkuyla yaklaşılan şeyler. Hadi bu prensipleri ve amaçlarını inceleyelim.

Encapsulation (Kapsülleme)

Türkçe’de kapsülleme veya sarmalama olarak bilinen encapsulation prensibi class’ın property’lerini korumaya almasıdır. Bu korumayı sağlamak için private ve protected access modifier’larını (erişim belirteci) kullanırız. Eğer bir property’i tanımlarken private kullanırsak o class dışında hiçbir yerden erişim sağlanamaz. Protected kullanırsak da yalnızca subclass’lar ve aynı package’da bulunan classlar tarafından erişilir. Encapsulation prensibinin maksadı class’a ait property’lere her classın istediği gibi erişmesini ve değiştirmesini engellemektir. Erişim kısıtlanmayacaksa bile erişimi getter/setter methodları gibi yöntemlerle kontrol altına almaktır. Zaten encapsulation prensibine gözümüzün en aşina olduğu yer getter/setter methodlarıdır. Bu prensiple alakalı biri getter/setter methodları olmak üzere iki örnek vereceğim.

public class Personel {
    
    private Long personelNu;
    private String isim;
    private String soyisim;
    private String departman;

    public Long getPersonelNu() {
        return personelNu;
    }

    public void setPersonelNu(Long personelNu) {
        this.personelNu = personelNu;
    }

    public String getIsim() {
        return isim;
    }

    public void setIsim(String isim) {
        this.isim = isim;
    }

    public String getSoyisim() {
        return soyisim;
    }

    public void setSoyisim(String soyisim) {
        this.soyisim = soyisim;
    }

    public String getDepartman() {
        return departman;
    }

    public void setDepartman(String departman) {
        this.departman = departman;
    }
}

Yukarıdaki örnekte gördüğünüz gibi hiçbir classın Personel property’lerine doğrudan erişim hakkı yoktur. Erişimi yalnızca getter/setter methodlarıyla yapabilirler. Tahmin edileceği gibi getter/setter methodları private olamaz.

public class Sayac {

    private Integer sayac;

    public Sayac() {
        sayac = 1;
    }
    
    public Integer sayacArtir(){
        return sayac++;
    }

    public Integer getSayac() {
        return sayac;
    }
}

Yukarıda ise örnek bir Sayac classı bulunmakta. Diğer class’lar erişim sağlanmak istediğinde getter methodunu, sayacı artırmak istediğinde ise sayacArtir() methodunu kullanmak zorunda. Yani herhangi bir class’ın ornekSayac.sayac += 1 gibi bir şey yazmaya yetkisi yok. Çünkü eğer herhangi bir class bunu yazabilirse sayacı istediği gibi manipüle edebilir. Bu sebeple encapsulation’ı kullanarak Sayac’ın property’sini korumaya almış oluyoruz.

Inheritence (Kalıtım)

Adından tahmin edilebileceği üzere herhangi bir class’ın üst class’larına ait olan method ve propertyleri kalıtım yoluyla almasıdır. Günlük hayattan bir örnekle anlaması çok daha kolay. Telefon adında bir class düşünelim. Bu class tüm telefonlarda ortak olan bazı property’leri ve method’ları barındıracak. aramaYap(); mesajGonder(); methodları ve imeiNumber property’sini örnek verebiliriz. Bu class’ın tüm alt class’ları bu methodları ve property’leri kullanabilecektir. Böylece AkilliTelefon ve TusluTelefon classlarını tanımlarken kalıtım yoluyla alınan bu property’leri ve method’ları tekrar tanımlamak zorunda kalmayacağız.

Aşağıda örnek bir Telefon class’ı bulunmakta.

public class Telefon {

    protected String imeiNumber;

    protected void aramaYap(){
        System.out.println("Arama yapıldı.");
    }

    protected void mesajGonder(){
        System.out.println("Mesaj gönderildi.");
    }
    
    public String getImeiNumber() {
        return imeiNumber;
    }

    public void setImeiNumber(String imeiNumber) {
        this.imeiNumber = imeiNumber;
    }

}

Şimdi de bu class’ın iki subclass’ını tanımlayalım.

public class AkilliTelefon extends Telefon{
  
    protected void interneteBaglan() {
        System.out.println("İnternete bağlandı.");
    }
}
public class TusluTelefon extends Telefon {

    protected void yilanOyununuAc(){
        System.out.println("Yılan oyunu açıldı.");
    }
}

Şimdi de Test class’ını yazalım.

public class Test {

    public static void main(String[] args) {

        AkilliTelefon akilliTelefon = new AkilliTelefon();
        akilliTelefon.setImeiNumber("354816220461203");
        akilliTelefon.aramaYap();
        akilliTelefon.interneteBaglan();

        TusluTelefon tusluTelefon = new TusluTelefon();
        tusluTelefon.setImeiNumber("354816221162234");
        tusluTelefon.mesajGonder();
        tusluTelefon.yilanOyununuAc();
    }

}

Yukarıdaki örnekte görüldü üzere AkilliTelefon ve TusluTelefon class’larının instance’ları hem Telefon class’ına ait olan aramaYap() ve mesajGonder() methodlarını ve imeiNumber property’sini inheritence (kalıtım) yoluyla alıp kullanıyor hem de kendi class’larına ait olan interneteBaglan() ve _yilanOyunuAc()_methodlarını kullanıyor.

Abstraction (Soyutlama)

Bu prensibe doğrudan bir örnekle başlıyoruz. Aşağıdaki kod parçacığının ne yaptığını, ne işe yaradığını biliyor musunuz? Biraz incelerseniz ne iş yaptığını tahmin edeceksiniz ama buna hiç gerek yok. Ben değil, abstraction prensibi söylüyor bunu. Aşağıdaki kod parçacığı IDE’nizi her açtığınızda en az bir kere kullandığınız System.out.println(); methodunun arkaplanda yaptığı işlerin bir kısmı. Fakat siz System.out.println(); yazarken arka planda neler döndüğünü bilmeden yazıyorsunuz. Bunun sebebi abstraction prensibi. Daha derli toplu bir ifadeyle objelerin ayrıntılarıyla uğraşmak yerine yalnızca girdi ve çıktılarına odaklanarak tasarımı daha iyi oluşturmayı ve anlamayı sağlamaktır.

try {
      synchronized (this) {
          ensureOpen();
          textOut.write(s);
          textOut.flushBuffer();
          charOut.flushBuffer();
          if (autoFlush && (s.indexOf('\n') >= 0))
              out.flush();
    }
}
catch (InterruptedIOException x) {
    Thread.currentThread().interrupt();
}
catch (IOException x) {
    trouble = true;
}

Buna prensibe araba kullanmak örneği de verilebilir. Araba kullanan birinin gaz pedalına ve frene bastığında veya vites değiştirdiğinde neler olduğunu bilmesine gerek yok. Yalnızca girdi ve çıktıları öğrenerek rahatça araba kullanabilir.

Polymorphism (Çok Biçimlilik)

Polymorphism, (çok biçimlilik) methodların objeye göre farklı çıktılar üretmesi veya farklı işler yapmasıdır. Yani alışageldiğimiz gibi methodlara sabit görevler vermek yerine onlara çok biçimli (polimorf) davranacak şekilde bir esneklik vermektir.

Polymorphism Örnek 1 (Method Overriding)

İlk vereceğim örnek klişe ama -bence- konuyu en iyi anlatan örnek.

public class Hayvan {

    public void sesCikar(){
        
        System.out.println("Hayvan sesi.");
    }
}
public class Kedi extends Hayvan {

    @Override
    public void sesCikar() {
        System.out.println("Miyav");
    }
}
public class Kopek extends Hayvan {

    @Override
    public void sesCikar() {
        System.out.println("Hav Hav");
    }
}

Yukarıda Hayvan class’ı ve Kedi — Kopek subclass’ları var. Hayvan class’ının sesCikar metodu olmasına rağmen Kedi ve Kopek subclass’ları için özel sesCikar metodu tanımladık. Aşağıda ise Hayvan tipindeki değişkene Kedi instance’ı verince kedi sesi, Kopek instance’ı verince köpek sesi çıkaracak.

public class Test {

    public static void main(String[] args) {

        Hayvan hayvan = new Hayvan();
        hayvan.sesCikar();

        System.out.println("-------");

        hayvan = new Kedi();
        hayvan.sesCikar();

        System.out.println("-------");

        hayvan = new Kopek();
        hayvan.sesCikar();

    }
}

Yukarıdaki testin çıktısı aşağıdaki gibi olacaktır.

Hayvan sesi.  
— — — –  
Miyav  
— — –  
Hav Hav

Polymorphism Örnek 2 (Method Overloading)

Bu örnekte ise aynı isimde iki method var. Bu iki method’un biri iki Integer değeri alıyor, diğeri ise bir Integer değeri alıyor. Biz kaç parametre göndererek çağırırsak o method çalışacaktır.

public void yemekYe(Integer hamburgerSayisi, Integer kolaSayisi){
      System.out.println(((hamburgerSayisi*261)+(kolaSayisi*168)) + " kalori alındı.");
  }

  public void yemekYe(Integer hamburgerSayisi){
      System.out.println((hamburgerSayisi*261)+ " kalori alındı.");
  }

  public void test() {

      yemekYe(2, 1);
      yemekYe(2);

}

Bu yazı daha önce https://medium.com/@kamer.dev/oopnin-4-ana-prensibi-encapsulation-inheritence-abstraction-polymorphism-712ed2fbac7e adresinde yayınlanmıştır.