Nesneye Yönelik Programlama Temelleri

1 Şubat 2019

Bir iş arayan ve birkaç mülakata girmiş olan herkes muhtemelen klişe soruların ne kadar can sıkıcı olabileceğini tecrübe etmiştir. Bu soruların bir mülakat zorunluluğu olduğu bilinmesine rağmen, cevaplayan taraf sırayı aldığında canı ciddi şekilde sıkılabilir.

Bu klişe sorular sektörüne göre değişir. Bugün, 1990'lardan bu yana en popüler yazılım sektörü mülakat konularından biri hakkında bir blog yazısı doldurmaya çalışacağım: Nesneye Yönelik Programlama Kavramları. Umut ediyorum ki temel kavramlarıyla başlayacağım bu blog yazısını, daha gelişmiş konular içeren başka içeriklerle de geliştireceğim.

Nesneye yönelik programlama, nesnelerin iletişimine dayanan bir programlama paradigmasıdır. OO paradigmasını kullanan bir program özünde bir nesne koleksiyonudur. Nesne üretmeye yarayan sınıf kavramı, şablon veya taslak olarak düşünülebilir.

OO yazılımında, tavuk ve yumurta ikileminden farklı olarak, ilk neyin ortaya çıktığını biliriz-sınıf. Bir nesne sınıfsız oluşturulamaz. Nesne oluşturmadan önce bir sınıf tasarlamanız gerekir.

Hayattaki hiçbir şey mutlak iyi ya da kötü olmadığından, OOP'nin de iyi ve kötü tarafları vardır. Bir yandan, yeniden kullanılabilir kodlara sahip, daha okunaklı ve daha sürdürülebilir olarak OOP programlarının uzun vadede verimliliği artırdığını söyleyebiliriz. Öte yandan, daha yavaş ve boyutlu yazılım, daha dik öğrenme eğrisi ve zorlu tasarım süreci yarattığını da rahatlıkla söyleyebiliriz.

İlk önce en temel kavramları açıklığa kavuşturalım, çünkü süslü isimleri ile bu kavramlar ilk kez karşılaşanlar için hiçbir şey ifade etmeyebilir (bu kavramları wiki sayfalarından kontrol etmek problemi iki katına çıkarabilir), her ne kadar sürekli karşılaşan aktif geliştiriciler için çok açık olsalar da.

Temel Kavramlar

Nesneye yönelik programlama dilleri, şu ilkelere göre tanımlanır: Soyutlama, kapsülleme, kalıtım ve çok biçimlilik. Bu nedenle, eğer bir dil veya program bu kavramları tam olarak kullanmıyorsa, tamamen nesneye yönelik sayılmaz.

Başka bir deyişle, eğer bu özellikler programınızda yok veya yetersiz seviyede uygulandıysa, sadece sınıflar ve nesnelerle yazılmış bir prosedürel programa sahip olacaksınızdır. Bir programlama dilinin nesneye yönelik programlar geliştirmek için uygun olması, bu dilleri kullanarak uyguladığınız her programın nesneye yönelik olacağı anlamına gelmez.

Daha kötüsü ise, bir programcının programın sadece belli kısımlarında nesneye yönelik programlama kavramlarını kullanarak, programı hem nesneye yönelik hem de yordamsal programcılar için anlaşılması zor hale getirmesidir.

Soyutlama

Soyutlamanın asıl amacı gereksiz ve karmaşık kısımları gizlemektir. Soyutlama, yalnızca nesne yönelimli programlama ile sınırlı olmayan ve gerçek dünyada da kolayca bulunabilen genel bir kavramdır.

Bir bilgisayara elektrik gerektiğinde, bilgisayarın güç kablosunu prize takmak, elektriğe ulaşmak için yeterlidir. Elektriğin şehre, sokağa, ve dairenize nasıl ve nereden ulaştığı gibi detayları bilmeye gerek yoktur. Bu detaylar kullanıcılara soyutlanmıştır.

Programınızda yerel bir kod (yalnızca belirli bir platformda çalıştırılabilen kod) kullanıldığında, bu bölüm farklı bir sınıfta yalıtılmalı ve programın ana sınıflarından çıkarılmalıdır. Örneğin, belirli bir donanımın seri portuna erişmek istiyorsanız, bu çözümü sağlayan bir wrapper sınıf, daha sonra birincil sınıflarınıza enjekte etmek üzere oluşturulur ve bu durum için şık bir çözüm elde edilmiş olur.

Benzer şekilde, nesne yönelimli dünyada soyutlama, iç uygulama ayrıntılarını sınıfın kullanıcılarından gizlemek anlamına gelir.

Soyutlama hem kapsülleme hem de veri gizleme ile alakalıdır.

import java.security.*;
..
byte[] bytesOfMessage = myString.getBytes("UTF-8");
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(bytesOfMessage);
Java ile md5 hash oluşturma.
MessageDigest sınıfının digest yönteminin detayı kullanıcılara soyutlanmıştır.

Kapsülleme

Kapsülleme, alakalı verileri bir arada toparlarken bir nesnenin değişkenlerine erişimini kısıtlamaktır. Bir nesne ilgili verilerle doldurulmalı ve durumu ancak kendisi tarafından değiştirilebilir olmalıdır.

Burada dikkat edilmesi gereken önemli nokta, değişken kullanımını ve modifikasyonunu diğer nesnelere kapatmak, public yöntemleri kullanarak nesneye yeterli bilgi vermemiz ve nesnenin bizim için hesaplamaları yapmasını sağlamaktır. Bunu elde edebilmek için, OO tasarım ilkelerini akılda tutmak ve OO tasarım sürecince verimli zaman geçirmek gerekir.

Bir veri türü veya yöntemi public olarak tanımlandığında, diğer nesneler doğrudan buna erişebilir. Bir veri türü veya yöntemi private olarak tanımlandığında, yalnızca kendisi erişebilir. Protected olan başka bir erişim belirleyici, yalnızca türetilmiş nesnelere erişim izni verir.

Kalıtım ve Kompozisyon

Yazılım mühendisliğinin temel prensiplerinden biri DRY (Don't Repeat Yourself) prensibidir. Bu, OOP paradigmasının yardımı ile büyük ölçüde başarılabilir; özellikle de kalıtım kavramıyla. Kalıtım, ilgili olan sınıfları birbirinin üstüne koyarak sınıf ilişkilerini tanımlar ve kod tekrarı yerine önceden yazılmış kodun tekrar kullanılmasını sağlar.

Bazı diller (örneğin, Java ve Objective C) sınıfları yalnızca bir üst sınıfla kısıtlarken, bazıları da (C ++ gibi) çoklu kalıtıma izin verir.

Kalıtım ilişkisi is-a şeklindedir. Bir alt sınıf bir üst sınıftan miras aldığında, bu temel olarak alt sınıfın, üst sınıfın yapabileceği her şeyi yapabileceği anlamına gelir.

Bir başka sınıf ilişkisi olan has-a, kompozisyon konseptinin objelerin kendi özünde başka objeleri bulundurmasıyla elde edilir. Yani nesneler başka nesnelerden inşa edilir.

Kalıtım, üst sınıfların enkapsülasyonunu zayıflatarak kırılgan üst sınıflar oluşturur. Üst sınıf ve alt sınıfları birbirine sıkıca eşlediğinden, üst sınıf değiştirildiğinde kontrol edilmesi gereken ilk şey, alt sınıflarında oluşabilecek dalga etkisi değişikliklerdir. Ek olarak, türetilmiş sınıfların testleri, üst sınıf değiştirildiğinde tekrarlanmak zorundadır.

Kompozisyon, sistemleri daha az karmaşık parçalar kullanarak oluşturmayı sağlar. Asıl avantajı oluşturan ana kısım, alt sistemler ana sistemden bağımsız olduğundan, alt sistemlerin testi ve bakımı da bağımsız olarak yapılabilir. Sistem daha küçük alt sistemlere bölündüğünde, geliştiricinin sistemi kavrama, sürdürme ve test etmesini kolaylaştırır.

Kompozisyon, bir yazılım tasarımcısı olarak, yazılım karmaşıklığı ile savaşmak için cephaneliğinizde sahip olduğunuz temel stratejilerden biridir.

Polimorfizm

Polimorfizm, Yunanca'da, birçok şekle sahip olmak anlamına gelir. Programlama jargonunda aynı kod parçası, operasyonlar veya nesnelerin farklı çalıştığı durumlar için kullanılır.

Özel (Ad Hoc) Polimorfizm

Özel (ad hoc) polimorfizm, aynı ada sahip farklı argümanlara sahip işlevleri ifade eder. Operator overloading olarak da bilinir ve derleme zamanında (statik bağlama) gerçekleşir.

int sum (int a, int b) { // int değerlerinin toplamı
  return a + b;
}

double sum (double a, double b) { // double değerlerinin toplamı
  return a + b;
}

int main() {
  cout << "The sum is " << sum(1, 2); // İlk yöntem tarafından hesaplandı
  cout << "The sum is " << sum(1.2, 2.1); // İkinci yöntem tarafından hesaplandı
}
Ad hoc Polimorfizm Örneği

Zorlama (Coercion) Polimorfizmi

Zorlama (coercion) polimorfizmi, bir değerin farklı bir veri tipine dönüştürülmesidir. Bu belirgin veya dolaylı olarak olabilir. Bu da derleme zamanında gerçekleşir.

double x, y;
x = 3;	// dolaylı coercion (coercion)
y = (double) 5;	// belirgin coercion (casting)
Zorlama (coercion) Polimorfizm Örneği

Bazen coersion ve overloading kombinasyonu belirsizlikle sonuçlanabilir.

int sum(int a, int b) { return a + b; }
double sum(double a, double b) { return a + b; }

int main() {
  cout << "Sum = " << sum(1, 1.2);
}
Belirsizten dolayı derlenmez.
Sebebi ise, C++ dilinin hem 'int double' hem de 'double int' dönüşümlerine izin veriyor olması.
Bu nedenle derleyicinin sum(1, 1.2) yöntemi için iki olası yorumu mevcuttur.

Parametrik Polimorfizm

Parametrik polimorfizm, bir fonksiyonun veya veri tipinin genel olarak tanımlanabilmesini sağlar. Böylece giriş verileri, türünden bağımsız olarak genel bir şekilde kullanılabilir. Tam statik tip güvenliğini korunurken aynı zamanda dile de anlam kazandırır.

class LinkedList<T> {
    class Node<T> {
        T elem;
        Node<T> next;
    }
    Node<T> head;
    void insert() { ... }
}
C++ dilinde Templates, Java ve C#' da Generics parametrik polimorfizmin örnekleridir

Alt Tür (Subtype) Polimorfizmi

Alt tür (subtype) veya alt tür polimorfizmi (subtype polimorphism), sanal yöntemlerle çalışma zamanında (run-time) gerçekleşir. İşaret edilen veya referans gösterilen nesnenin (subtype) implementasyonu çağrılır. Polimorfizmden söz edildiğinde çoğu zaman bu tip ilk akla gelendir.

class Felid {
  public:
  virtual void meow() = 0;
};

class Cat : public Felid {
  public:
  void meow() { cout << "Cat meow!"; }
};

class Tiger : public Felid {
  public:
  void meow() { cout << "Tiger MEOW!"; }
};

int main() {
  Felid* cat = new Cat();
  Felid* tiger = new Tiger();

  cat->meow(); // Outputs Cat meow!
  tiger->meow(); // Outputs Tiger MEOW!
}
C++ dilinde alt tür (subtyping) polimorfizmine bir örnek

Eski Sistemlerle Karşılaştırma

Nesneye yönelik kavramlar ana akıma geçtiğinde, karşılaşılan meselelerden biri hali hazırda çalışan eski kodu yeni sistemde kullanabilmekti. Tüm eski kodları nesneye yönelik sürümlü haliyle değiştirmek ve eskisi gibi çalışmasını beklemek hem gereksiz hem de riskli olduğundan (yazılımla bağı olan herkes küçük değişikliklerin bile felaketlerle sonuçlanabileceğini bilir), eski kodu korumak ve tamamlayıcı olarak çalışmalarını sağlamak, deneyimli programcıların ilk tercihi oldu.

Yordamsal ve Nesneye Dayalı Programlama

Bir nesne, hem veriyi hem de davranışı kendi özünde tutar ve bu yordamsal ve nesne yönelimli programlama arasındaki farkı yaratır. Yordamsal programlamada veri ve davranış farklı yapılarda tutulmaktadır.

Yapısal programlamada veri genellikle globaldir ve bu da kontrol edilemeyen verilere yol açar. Global olarak erişilebilen veri, hangi fonksiyonun kendisini kullandığı konusunda neredeyse fark edilemez ve bu da yazılımın sürdürülebilirliğinin düştüğü anlamına gelir. Nesneler bu sorunu aynı yapıda ve toplayarak çözmeyi amaçlar.

Dahası