emrah dayioglu java html css js android

Diamond problem

"Deadly Diamond of Death" de denilen yazılım mimarisinden kayaklanan "object oriented programming" ile ilgili bir problemdir. Mesela doğadaki canlilari ifade eden "Canlı" diye bir sınıfımız olsun. Bu "Canlı" sınıfının altında bundan kalıtım yoluyla türetilmiş "Kedı" ve "köpek" diye iki sınıfımız daha olsun. Örneğimizde "Canlı" sınıfına "yemekYe()" diye bir metod oluşturalım. "kedi" sınıfının kendine has yemek yeme özelliği vardır. Bu nedenle "Canlı" sınıfının "yemekye()" metodunu "override" yaparak kendine has yemek yeme metodunu oluşturuyoruz. ayni şekilde "köpek" sınıfının da kendine has yemek yeme özelliği vardır. Bunun için de "Canlı" sınıfının "yemekye()" metodunu "override" ediyoruz. buraya kadar her şey yolunda gidiyor. (Buraya kadar olan kısmı Abstract class veya interface konusuna girmeden daha sade anlatmaya calıştım, yoksa bu konuda daha iyi yapı oluşturulabilir elbette)







class Canli{
   public void yemekYe(){
     // her canlı gibi yemek ye
   }
}

class Kedi extends Canli{
   public void yemekYe(){
     // kedi gibi yemek ye
   }
}

class Kopek extends Canli{
   public void yemekYe(){
     // kopek gibi yemek ye
   }
}

Kedi kendisine has yemek yeme özelliğine sahip, köpek de kendisine has yemek yeme özelliğine sahip. bunun dışında tüm hayvanların da yemek yeme özelliği kalıtım yoluyla diğer hayvan türlerine aktarılmak üzere mevcut duruyor. Fakat ileride evde beslemek üzere "evhayvani" adında bir sınıf daha yaratırsak ne olur? elbette tüm hayvanlar ev hayvanı değildir o nedenle "evhayvani" sınıfını "Canlı" sınıfından değil de "kedi" ve "köpek" sınıflarından kalıtım yoluyla türetmemiz gerekecek. (Elbette ideal bir mimaride ev hayvanı bir interface olmalı ama ideal olmayan bir problemi ortaya koymak icin bu örneği verdim). Ev hayvani olan kedi doğada yasayan vahşi hemcinslerinden farklı yemek yeme davranışına sahip. Aynı şekilde köpek de. Şimdi bizim bu "evhayvanlari" iki sınıftan türetildi bunlar "kedi" ve "köpek", ikisi de "yemekye()" metoduna sahip. eğer ev hayvanımıza yemek ye dersek bu ev hayvani kedi gibi yemek ye metodunu mu çalıştıracak yoksa köpek gibi mi? bu bir cikmaz, "Canlı" sinifindan cikan iki dal "kedi" ve "köpek" diye ayrildi ama tekrar bu dallar "evhayvanlari" sinifinda birlesti, tipki bir baklava dilimi sekli gibi <> yada elmas (diamond) sekli gibi birlesti. bu durum özel olarak cözüm üretilmesi gereken bir problem.



class Canli{
   public void yemekYe(){
     // her canlı gibi yemek ye
   }
}

class Kedi extends Canli{
   public void yemekYe(){
     // kedi gibi yemek ye
   }
}

class Kopek extends Canli{
   public void yemekYe(){
     // kopek gibi yemek ye
   }
}
// aslında java da coklu kalıtım yanı multiple inharitance yoktur. 
// Burada olsaydı ne olurdu açıklamak için ekledim
class EvHayvani extends Kedi, Kopek{
   public static void main (String[] args){
     EvHayvani evHayvani = new EvHayvani();
     evHayvani.yemekYe(); // iste burada ne olacak, bu ev hayvani hem kedi hem de köpek, nasil yemek yiyecek ?
   }
}


İşte bu problemle karşılaşmamak için java dili iki ayrı sınıftan kalıtım almayı engellemiştir yani java dilinde "multiple inheritance" yoktur. Ideal yapı aşağıdaki gibi olmalıdır. Yani Canlı ismiyle bir abstract class olustururuz cünkü sadece canlı yoktur, canlı olan ya kedidir ya köpek, bu nedenle abstract. Ev hayvanı diye interface olustururuz cünkü ev hayvanı olmak kedi ve köpegin özelligidir, Kedi ve Köpegin kalıtımsal kökeni degildir. Diger yandan Canlı olmak kalıtımsak kökenidir. Kedi ve Köpek sınıfları da Canlı sınınının alt sınıfı yani kalıtımla almış ama EvHayvanı özelligini interface nedeni ile uygular.




abstract class Canli {
 void yemekYe(){
  System.out.println("Canli gibi yemek ye");
 }
}

interface EvHayvani{
 public void yemekYe();
}

class Kedi extends Canli implements EvHayvani {
 public void yemekYe(){
  System.out.println("Kedi gibi yemek ye");
 }
}

class Kopek extends Canli implements EvHayvani {
 public void yemekYe(){
  System.out.println("Kopek gibi yemek ye");
 }
}


Diamond problem yapısal olarak java dilinde gercekleştirilebilir degildi ta ki "java 8"'e kadar. java 8 de "interface"ler de "uygulanmış" metodlar barındırabilir. Elbette önceki örnekte dedigim gibi javada "extends" anahtar kelimesi ile sadece bir sınıftan inheritance yapabiliyoruz. Ama bu interface kullanımında böyle değildir, bir sınıf birçok "interface"'i "implements" anahtar kelimesi ile uygulayabilir. java 8 e kadar "interface"'ler metod barındırmadığı için bir mimari sözleşme veya yaptırım olarak görev almış fakat java 8 de "interface"'ler metod barındırabilmesi ile işlevsellik kazanmıştır. bu da "diamond problem" başka bir deyişle "deadly diamond of death" probleminin önünü açacak olmuş ama bu sefer de yapı değil compiler buna izin vermez.


Yukarıdaki herseyi bir kenara bırakıp bu sefer farklı bir örnekle farklı bir mantık kuralım. Bir insanımız var "Insan" sınıfı ile türetebileceginiz. Bu insan "ArabaSurebilir" interface ile araba sürme özelligi kazanıyor ve araba sürmenin bir olayı olarak ayak ile frene basabiliyor. Aynı insanımız bisiklet de sürebilir ve bu özelligi "BisikletSurebilir" interface ile alıyor ama bu sefer frene basma isini eli ile yapıyor.


interface ArabaSurebilir {
 default void freneBas(){
  System.out.println("Ayak ile frene bas");
 }
}

interface BisikletSurebilir {
 default void freneBas(){
  System.out.println("El ile frene bas");
 }
}

class Insan implements ArabaSurebilir, BisikletSurebilir{
 public static void main(String[] args){
  Insan insan = new Insan();
  insan.freneBas();
 }
}
Simdi biz bu adama frene bas demek istedik ama compiler buna izin vermez ve söyle bir hata fırlatır

C:\javaExample>javac Insan.java Insan.java:1: error: class Insan inherits unrelated defaults for freneBas() from types ArabaSurebilir and BisikletSurebilir class Insan implements ArabaSurebilir, BisikletSurebilir{ ^ 1 error 

Hata veriyor cünkü diyor ki sen bu adama "frene bas" diyorsun da bu adam hem araba hem bisiklet kullanabilen bi adam, simdi frene bas diyince bu adam bundan ne anlasın, el ile bisiklette gibi mi bassın, yoksa arabadaki gibi ayakla mi bassın. Olmaz "Deadly Diamond of Death" yarattın yapını düzelt diyor.

Global değişkenler

Programın her faaliyet alanı yani kapsamı (scope) tarafından erişilebilen değişkenlere denilir. global değişkenlerin kullanımı programın her tarafından her fonksiyonundan erişilmesi, erişim sağlarken kısıtlamaları sağlayan kapsamları (scope) düşünmeden çok rahat bir şekilde erişilmesi avantaj gibi düşünülse de çok kötü bir programlama pratiğidir. birçok programlama dili tarafından desteklense de deneyimlerime göre mümkün olduğunca kullanılmaması gerekir çünkü programın karmaşıklığını kullanıldığı anda katlayarak arttırır. bildiğim kadarı ile c/c++, php, python gibi diller tarafından desteklenirken, bilinenin aksine java tarafından desteklenmez. javada her değişkenin yasam süresi dahil olduğu sınıfın yasam süresi ile kısıtlıdır. fakat bu kısıt çeşitli yöntemlerle aşılabilir ve değişken (aslında bir bilgi) global hale getirilebilir.

peki bu global değişken kullanmak neden iyi değil? benim programlamaya bakış açıma göre, bir program gerçek dünyanın mümkün olduğunca soyut hali olmalı, ama gerçeklik kurallarından çıkmamalı. o yüzden bu değişkenlerin neden olduğu problemi gerçek dünyadan örneklerle anlatabilirim. değişkenleri bir telefon gibi düşünelim. cep telefonları lokal değişkenlerdir çünkü size aittir, o telefon üzerindeki kontrol sizindir. bu telefonu kullanacak kişi önce size sormalı, sizin için de uygunsa kullanabilmelidir. peki bu telefonu global yaparsak ne olur. bir aile düşünün, herkes ayrı ayrı bir cep telefonuna sahip olmak yerine sadece bir global telefonla bu durumu çözüyorlar. bu konuda da avantaj olarak gösterdikleri ama avantaj olmayan nedenleri ise sunlar. telefona ailedeki herkes ulaşabilir ve kullanabilir çünkü telefonun sahibi belirli biri değil bu da erişim kolaylığı sağlıyor. ailedeki herkese ayrı telefon yerine 1 telefon kullanılması hem daha tasarruflu, hem de fatura için birçok telefonu kontrol etmek yerine tek telefonu kontrol ettiğimiz için karışıklık da azalıyor. bu örnekleri verince 90’lı yıllardaki evlerdeki sabit telefonlarla yaşadığımız problemler aklımıza gelmiştir. arkadaşımızı acilen aramamız gerekir ama evin annesi gün arkadaşları ile bitmek bilmeyen konuşma yapmaktadır, arayamayız. ya da önemli bir yerden telefon bekleriz ama evin genç oğlu kız arkadaşıyla gereksiz bir konuşma yapmaktadır, "önce sen kapat...", "yok sen kapat..." gibi. peki ya evin babasi; evin babasi da telefon rehberindeki kisileri isimlere göre degil de soyadi sirasina göre alfabetik siralamistir, soyadini bilmediginiz sadece ismen tanidiginiz kisiyi rehberde bulamazsiniz. evin kücük kizi ise telefonun cagri melodisini degistirmis, bir yerden bir melodi duyuyorsunuz ama o melodinin telefondan mi geldigini ayirt edemiyorsunuz. tam bir kaos. temelde bu problemler iki nedene dayanir 1. neden; telefonu baskalari kontrol eder, bir seyleri degistir ama siz ne oldugunu bilemezsiniz. 2. neden; telefonu siz kontrol eder, birseyleri degistirirsiniz ama baskalari ne oldugunu bilemez. bunun yerine herkesin ayri bir telefonu olsaydi ve her degisiklik icin önce telefonun sahibinin olurunu alsaydik bu karmasalar yasanmayacakti. bu örnegi proglamlamaya aynen aktarabiliriz. bir fonksiyon global bir degiskene erisebilir, bu degiskeni istedigi gibi degistirebilir ve diger fonksiyonlarin bundan haberi bile olamaz. ya da bir degisken kullanmak istiyoruz ama bu degisken baska bir fonksiyon tarafindan kullaniliyor, kullanmamiza izin verilirse diger fonksiyonun o an yapmakta oldugu isi bozabiliriz, kullanmamiza izin verilmez ise o fonksiyonun isinin bitmesini beklemeliyiz, yani senkronize yürütme/asenkronize yürütme (syncronized thread/asyncronized thread). degiskende her bir degisiklik ona yeni bir durum (state) kazandiracaktir. her degiskeni kullanmadan önce ne durumda oldugnu kontrol etmemiz gerekir cünkü degiskenin durumu baskasi tarafindan degistirilmis olabilir. mesela "saat" degiskenine "11" degerini attik. daha sonra fonsiyonumuz (ya da methodumuz) kodu yürütürken arkada bir baska fonksiyon "saat" degerini "null" olarak sifirladi. eger fonksiyonumuz bu "saat" degerini arttirmaya calisirsa hata alacaktir. bu degeri kimin ne zaman degistirdigini de bilemeyecegiz.

özetle, eğer global değişken kullanırsan fonksiyonlar arası bağımlılık artacaktır bu da hata ayıklamayı (debugging) zorlaştıracak, program yapısı daha karmaşık olduğu için "spagetti code" kıvamına gelecek, "modularity" düşecektir. fonksiyonlar içinde ise objenin durumunu (state) her isleme almadan önce kontrol etmemiz gerekecek, her olasılığa göre "exception handling" yapmamız gerekecek bu da fonksiyon içinde de daha fazla kod yazmamıza ve karmaşıklığa neden olacaktır. dolayısı ile büyük resimden baktığımızda kodun "maintainability", "extendibility" ve "reliability" düşecektir. tüm bunları hesaba katarsak sadece değişkene erişimimizi kolaylaştırmak için birçok önemli kriterden feragat etmemiz, kötü kod yazmamız olası olur.

Mutlak dogru

Yaşadığımız tüm problemlerin anahtarıdır "mutlak doğru". En büyük yanılgımız da "doğru" ile "mutlak doğru"yu birbirine karıştırmamızdır. Belki de bunun en büyük nedeni beynimizin bize oynadığı oyun, evreni kendi perspektifimizden algılamamızdır.


Peki doğru nedir, mutlak doğru nedir? Araba kullanmayı çok severim, arkadaşlarım benim kullandığım arabaya bindiklerinde "ne kadar yavaş araba kullanıyorsun" derler, doğrudur. Ama annem benim kullandığım arabaya bindiğinde "yavaş kullan oğlum, çok hızlı kullanıyorsun" der, bu da doğru herhalde. ama burada bir gariplik oluşur, çünkü ben hem hızlı hem de yavaş mi kullandım? ben de kendi kendime diyorum ki "yok, ben iki durumda da ayni hızda kullandım" ve bunun da doğruluğuna inanıyorum, bu da doğru. yani ben hem hızlı, hem yavaş, ne hızlı, ne de yavaş kullandım. hepsi birbiri ile çelişir durur, ama herkesin dediği doğrudur. yani doğru (right) bir olgunun (fact) gözlemci (observer) akli tarafından yoğrulması ile oluşur. r = f + o. doğrunun açıklaması budur r = f asla değildir. bizim en büyük yanılgımız bu.


hayat r = f + o formülü ile giderken bir gün esim bindi arabaya, acelesi vardı "ne kadar yavaş kullanıyorsun, sen hiç böyle kullanmazdın" dedi. tamam dedim esimin (o) bakış acısından araba kullanmam (f) yavaştı (r). her şey yerine oturuyordu. ama günler sonra esim daha sakin bir günde ben araba kullanırken "ne kadar hizli kullanıyorsun, bizi tehlikeye atma, sen hiç böyle kullanmazdın" dedi. bu sefer esimin (o) bakış acısından araba kullanmam (f) hızlıydı (r) . bu iki durumda da esim (o) hiç değişmedi, araba kullanmam (f) da değişmedi, ama doğru (r) değişti. burada r hem hızlı hem de yavaş nasıl olabilirdi. işler giderek karışıyor. işte burada isin içine zaman (t) girdi. zaman göreceliydi (relative), ve her şeye görecelilik katıyordu. yani zaman her şeyi kırıp bükebiliyordu. her şey ayni kalsa bile doğruyu yanlış, yanlışı doğru yapıyordu. yani r = f + o + t. ben eskiden (t) araba kullanırken (f) esimin bakış acısından (o) arabayı yavaş (r) kullanıyordum. ama simdi (t) araba kullanırken (f) esimin bakış acısından (o) arabayı hızlı(r) kullanıyorum. işler iyice karıştı, esim hakliydi.


ama benim kafamı kurcalayan bir şey daha vardı. arabayı kullanan bendim, orada bir (o) daha vardı. bana göre ise arabayı ne hızlı ne de yavaş kullanıyordum, tam olması gibiydi. arkadaşım mi doğruydu, annem mi, esim eskiden mi doğruydu, simdi mi, yoksa ben mi doğruydum? bu doğru nasıl birseydi ki hiçbiri doğru değildi. yani hiçbiri "mutlak" değildi. hep değişir, hangisi gerçekten doğru kimse bilemezdi. çünkü isin içinde zaman (t) var hiç durmuyor ve hep değişiyor, birileri var (o) bu da değişiyor, araba kullanmam (f) değişmese de doğru (r) hep değişiyor.


peki doğru gerçekten bilmemizin imkanı yok mu? var elbette. onu her gün kullanıyoruz ama bu durumda tembellik ettiğimizden kullanmak istemedik. yol gösterici matematik, neye karar verirsek verelim asla doğruluğuna emin olamayız, emin olmak için matematik bize yol gösterir, matematik tarafından doğrulanan her şey "mutlak doğru"ya yaklaşır. o nedenle en iyisi matematiğin araba sürerken iz düşümü olan hız göstergesine bakmakta yarar var, o zaman anlardık hızlı miyiz, yavaş miyiz. peki her durumda matematik bize yok gösteremiyor, çünkü olguyu matematik haline getiremiyoruz. tembellik etmeyelim mümkün olduğunca her şeyi matematikleştirelim. tabi bu da insan beyninin limitlerine bağlı, her şey matematik olamıyor. o durumda da bize beynimizin kurgulayamadığı matematiğe yardımcı olması için istatistik devreye giriyor. matematik yapamıyorsak istatistik yapalım. bunların ikisi ile doğrulayamadığımız bir durum var ise en azından doğrumuza körü körüne inanmayalım. hep doğrumuzun doğru olmadığı aklimizin ucunda dursun.


insanları bakış açıları bakımından ayırabiliriz, r = f seklinde düşünenler, bunlar "bu doğru, başka türlü olması imkansız" derler. r = f + o seklinde düşünenler, "bu benim açımdan doğru, senin açından yanlış ama bu benim görüşüm" derler. r = f + o + t seklinde düşünenler, "bu şimdilik benim açımdan doğru, ama senin açından yanlış olabilir, ilerde elbette fikirlerimiz değişebilir" derler. bir de matematiğin dilinden konuşanlar var, 2 + 2 = 4 dür derler. (o) değişse de, (t) değişse de 2 + 2 = 4 dür. çünkü matematik öyle diyor, sadece benim için değil, sadece senin için de değil, her zaman herkes için 2 + 2 = 4 dür.