Aşağıdaki yazı, bir yazılım şirketinin blogunda gördüğüm bir “challenge” üzerine düşünüldü
ve kaleme alındı.
Pek çok OO programcısının benzer sıkıntıları yaşadığını düşünüyorum.
Structured ve OOP kıyaslaması denemesi :
1- Data Oriented dizayn:
Her iki programlama modeli’de Data-Oriented yaklasima izin verir.
Yani uygulamanızın işleyeceği ve üreteceği veriyi temel alarak geliştirme yapabilirsiniz.
Ama OOP bunu zorunlu kılar.
Information Hiding ve Sınıf Invariantları:
——————————————
OOP yaklaşım, fonksiyonları sınıf yapısı içerisine dahil eder.
Bu sayede paketlenen veriye erişim hakları sınırlanabilir,
yani information-hiding becerisi sunar.
Bu sayede “encapsulated data” (yani STATE) üzerinde,
sınıf invariant’ları denilen, verinin çeşitli “iyi durumları” arasında güvenli geçişler mümkün olur.
Örnek : Elektrik düğmesi ON olduğunda lamba yanmalıdır,
düğmeyi ON haline getiren ama
ampulün State’ini modifiye etmeyi unutan kod probleme yol açacaktır.
OOP ile bu değişkenleri private yapıp,
sadece sağladığınız, bu invariantı koruyan bir fonksiyon ile ulaşıma izin verirsiniz.
2- Design By Contract (“Don’t break the client”):
————————————————
Bir şekilde dağıttınız kod,
sadece sağladığınız bir dizi işlev değil,
ilerisi için verdiğiniz vaatler anlamına gelir.
Dependency problemleri olarak karşımıza çıkan durumlar
bu vaatlerin tutulmamasından kaynaklanır.
Uygulamanızın, 3.2 sürümünü kullandığı bir kütüphane,
kütüphanenin 4.0 sürümü yüklü olan bir makinada problem çıkartıyorsa,
kütüphanenin yazarı, sınıf ya da method invariantları ile ilgili
değişiklikler yapmış demektir.
Örneğin: önceki sürümde null arguman kabul eden bir fonksiyon,
sonraki sürümde hataya yol açıyor olabilir.
Peki yazılımınız, kütüphanenin yeni sürümündeki veri ve fonksiyonları neden kabul ediyor ?
- Structured programlama yapılmış ise, veri-tipi ve fonksiyon tanımı örtüşüyordur.
- OOP da ise Sınıf hiyerarşisi izin veriyordur.
Structured Programlamada;
Değiştirdiğiniz struct ve fonksiyonlarınızı,
eski kodu değiştirerek sunabilirsiniz,
içerik farklı adlar aynı kalır.
Eski client’lar bu kodu sorunsuz kullanabilir.
Fakat programcı açısından bir fayda yoktur,
eski kodun bir kopyasının alınıp sistemden çıkartılması gerekir.
çünki code-reuse imkanı yoktur.
ama buna ragmen OOP yapılıyormuş gibi,
eski struct ve fonksiyonların invariantları kollanmak zorunda kalınır.
İkinci yaklaşım ise, farklı yeni struct ve fonksiyonlar
farklı adlarla kodlanır.
yani yeni veritipi ve fonksiyonlar oluşturulur.
Client uygulamalar bu veri tipinin/fonksiyonların adını bilmedikleri için
referans edip kullanamazlar.
Kullanmak istiyorlarsa, client kodu değişikliklere uğramalı ve yeniden derlenmelidir.
Yani eski ve sağlam client uygulama
bir miktar çaba ile, yeni ve sağlam hale getirilmelidir.
OOP da :
Open-Closed prensibi işler,
inheritance aracılığı ile
Ata sınıfa, yeni State bilgileri ve beraberinde yeni davranışlar ekleyebilir.
eski sınıf üzerinde değişiklik yapılmamıştır,
yeni sınıf ilk atanın yerine geçebilmektedir ve
ve client kodu en üst seviye abstraction’a göre kodlanmıştır.
yani client kodu çok az (Factory sınıflarında) ya da hiçbir değişiklik yapılmadan
(Konfigürasyon dosyaları aracılığı ile yapılan Dependency Injection) kullanılabilir.
Fakat önceki sınıfın (ata sınıf),
class ve fonksiyon invarianlarının korunması gereklidir.
Yani child sınıfın bir üyesi,
ata sınıfın “kötü” diyeceği bir state içerisinde olamaz.
(Ne tür bir lamba olursa olsun, power ON ise yanmalıdır.)
Ayrıca Child sınıfın override ettigi bir fonksiyon,
ata sınıftaki pre-condition’ı daraltamaz, post-condition’ı genişletemez.
(null arguman örneğinde bu kural çiğnenmiştir,
argumanın null olmaması gereği precondition’ı daraltır.)
Özetle sınıf ve method invariantları bize
nesne yönelimli programlamanın,
şekli bir uyumun ötesinde,
semantik bir uyum amaçladığını söylüyor.
Aynı durum Liskov’s Substitution prensibi olarak da dile getirilmiştir.
Problem :
————–
probleminiz, bu tarz soyutlamalara izin veriyor mu ?
programcı, problem domaini hakkında yeterli tecrübeye sahip mi ?
başta çok mantıklı gelen derin sınıf hiyerarşilerinin
ileride problem çıkartması durumunda
kendi kodunuz ve client kod
structured olandan daha fazla zarar görecektir.
Çünki OOP kodu, nesneler arasındaki kalıtım ilişkileriniz (OO Design) üzerine kurulmuştur.
C ile C++ karşılaştırması yapan bir söz bu durumu güzel açıklıyor:
“C ile ya da C++ ile bacağınızı vurabilirsiniz, ama bunu C++ ile yapmışsanız bacağınız kopar.”
Algı Yanılgıları:
—————–
İnsan algısının OOP mantığı ile ne kadar uyumlu olduğu söylenir.
Bu gündelik bilinç ve dil seviyesinde böyle olabilir.
Çünki bu seviyede beklentilerimiz
bağlama göre ve çok hızlı şekilde değişebilir.
Ama kod üzerindeki soyutlamalar/genellemeler
kodun ömrü boyunca beklenen faydaları sağlamalıdır.
Başlangıçta çok doğal gelen;
Dikdortgenden türüyen Kare sınıfı,
ya da Elipsden türüyen Çember sınıfı örneklerinin yarattığı karmaşa
bunun kanıtı olarak gösterilebilir.
http://en.wikipedia.org/wiki/Circle-ellipse_problem
İlginç Bir Örnek:
——————-
Semantik uyum ile ilgili bir örnek :
Car adlı bir sınıfınız var ve
SpaceCar adlı bir sınıfı bundan türetiyorsunuz.
Car sınıfı transport adlı bir metoda sahip,
yani transport işleminin detayları gizleniyor.
Dolayısı ile uçarak ya da karadan gitmenin bir farkı yok.
Daha sonra SpaceCar’ın, belli bir yükseklikten sonra
basınç farkı yüzünden
pencerelerinin açılmasının güvenli olmadığını fark ediyorsunuz.
Car sınıfının openWindows fonksiyonu
pencereleri kullanıcı istediğinde açıyordu.
SpaceCar, Car sınıfının openWindows metodunun
söz verdiği precondition’ı daraltılırsa ,
dizayn hatalı olur ve problem çıkartabilir.
Yapmaz ise araç güvensiz hale gelir.