Java – Object Oriented Kavramı I
Daha önce bir object oriented programlama (OOP) dili kullanmadıysanız muhtemelen Java ile ilgili yazacağım yazılarda zorlanacaksınız. Ben her ne kadar bilindiğini varsayacağımı söylesem de bazı temel kavramlara değinmem gerektiğini düşündüm. Çünkü OOP kullanılsa bile bazı kavramlar bilinmiyor, ezbere kullanılıyor. Object, class, inheritance, interface, encapsulation… gibi terimlerin ne anlama geldiğinden bahsederken bu kavramları gerçek hayattan örneklerle ilişkilendirerek iyi anlaşılmasını sağlamayı amaçlıyorum. Örnekler de tabiki Java syntax ‘ı kullanılarak verilecek. Terimleri anlatırken Türkçe’ye çevirmemeyi tercih ediyorum. Çünkü o zaman karışabiliyor, en iyisi orjinal haliyle öğrenmek.
Object:
OOP mantığını anlamanın temeli Object kavramını anlamaktan geçiyor…Object en basit şekilde düşünün. Yazılımı unutun. Etrafınıza bakın. Gördüğünüz şeylerin hepsi birer object‘tir. Masanızdaki kalem, bahçenizdeki ağaç, yoldaki araba, hepsi birer object‘tir. Bunların 2 ortak noktası var. Bu ortak noktalara odaklanacağız.
Birinci ortak nokta her object‘in özellikleri olduğudur. Örneğin kalemin rengi, ağacın boyu, arabanın markası gibi… Her object‘in özellikleri vardır. Bir diğer ortak nokta ise her object‘in bir davranışı, bir aksiyonu vardır. Örneğin kalem yazar, ağaç büyür, araba hareket eder… Bunları çoğaltmak mümkün, örneğin araba için fren yapmak, vites değiştirmek, yakıt almak, farları açmak gibi aksiyonlardan bahsetmek mümkündür.
Bu noktalardan bakılarak OOP dünyasını girebilirsiniz. Yazılım object‘lerinin de aynen gerçek dünyadaki object‘ler gibi özellikleri ve davranışları vardır. Yazılım object‘lerinin özellikleri field denen (bazı dillerde property de denir) alanlarda, davranışları ise method denen (bazı dillerde function da denir) yapılarda belirtilir.
Methodlar object‘in özelliklerini yönetmek için kullanılır ve object’in dış object‘ler ile ilişki kurabilmesini sağlar. Bu şekilde object‘in iç yapısı diğer object‘lerden gizlenmiş olur. Yani diğer object‘ler özelliklere ulaşamaz, sadece methodlar ile kontrollü bir şekilde iletişim kurarlar. Buna data encapsulation denir. Bu da object oriented programlamanın temellerinden biridir.
Encapsulation sayesinde özelliklere doğrudan erişilemez ve özellikler methodlar ile yönetilir. Örneğin arabada sadece 6 vites varsa ve siz vitesDegistir isimli methoda 7 parametresini gönderirseniz object “vitesi 7’ye atamazsınız” şeklinde uyarı verecektir. Bu sayede object‘lerinize bir iç kontrol mekanizması sağlamış olursunuz.
Kodları object kullanarak yazmanın çok etkili faydaları var;
- Modülerlik: Object‘leri uygulamanızdan bağımsız olarak tasarlayıp kodlayabilirsiniz. Object‘i bir kere oluşturduktan sonra uygulamanızın istediğiniz yerinde ulaşıp kullanabilirsiniz.
- Gizlilik: Object’in içinde ne işler döndüğünü bilemezsiniz. Bilmenize gerek yoktur. Ekipteki başka bir yazılımcı bir object tasarlamış olabilir. Bu object‘in özelliklerini bilmezsiniz, methodlarının nasıl çalıştığını da bilmenize gerek yoktur. Siz object‘i çağırır ve yapması gereken methodu söylersiniz, o da yapar…
- Kod Tekrarını Önleme: Bir object yazılmışsa, başka biri tarafından da yazılmış olabilir. Bunu her uygulamada kullanabilirsiniz. Örneğin uygulamanızda bir file object‘i kullandıysanız, bu object‘i başka uygulamalarda da kullanabilirsiniz. Tekrar yazmanıza gerek yoktur. Sonuçta bir file’ın hangi özellikleri hangi aksiyonları olduğu bellidir, bunları bir kere implement ettikten sonra tekrar tekrar kullanabilirsiniz.
- Problem Çözme: Sistemde bir hata oluştuğunda, muhtemelen bir object işini düzgün yapamıyordur. Bu durumda sadece o object‘i düzelterek veya değiştirerek tüm sistemi düzgün çalışır hale getirebilirsiniz. Yani bu gayet doğal değilmi. İşlemciniz bozulduğunda bilgisayarı toptan atıp yenisini almazsınız. Yeni bir işlemci alıp eskisi ile değiştirir ve sistemin çalışmasını devam ettirirsiniz. Yani sadece sorunlu object‘i değiştirdiniz. Aynı mantık…
Class:
Yine yazılımı değil gerçek dünyayı düşünün. Object başlığında bahsettiğimiz objectler yani kalem, ağaç, araba… Bu objectler gibi dünya da milyonlarca object vardır değil mi. Yani aynı özelliklere ve aynı aksiyonlara sahip birçok araba var. Her arabanın rengi, hızı, vitesi…vs var. Tek bir arabaya object denirken, tüm arabalar için genel bir tasarım, bir kroki yapılırsa buna class denir. Yani object bir class‘tan türemiş örnektir (instance). Class ise object‘lerin genel tasarımı, sınıfıdır. Araba için java’da şöyle bir class yazılabilir;
class Car { string color = "black"; int speed = 0; int gear = 1; void changeColor(string newValue) { color = newValue; } void changeGear(int newValue) { gear = newValue; } void speedUp(int increment) { speed = speed + increment; } void applyBrakes(int decrement) { speed = speed - decrement; } void printStates() { System.out.println("Color:" + color + " Speed:" + speed + " gear:" + gear); } }
Henüz java öğrenmediğiniz için burada syntax’a çok takılmayın. En başta bahsettiğim 2 noktaya dikkat edin. Arabanın özellikleri (color, speed, gear) ve dış dünyayla ilişkisini sağlayan aksiyonları (changeColor, changeGear, speedUp…) class yazarak tanımlanmıştır. Java’ya giriş yazımda her uygulamada bir main methodu olur demiştim ama burada yok. Çünkü bu bir uygulama değil, sadece Car Object‘lerinin nasıl olacağını gösteren bir tasarım, bir Class. Bunu asıl uygulamanıza import edeceksiniz ve bu tasarımdan arabalar üreterek kullanacaksınız.
Inheritance:
Bunu Türkçe’ye çevirerek kalıtım demekte bir sakınca yok. Çünkü aynen gerçek hayattaki kalıtım gibi bir çocuk evebeylerinin bazı özelliklerini alır, kendine has özellikleri de olur. Yazılımda da aynı şekilde bir class‘ın alt class‘ları olabilir. Bu alt classlar üst class‘ta tanımlanan field ve method‘ları kalıtım ile alır ve ayrıca kendine özel field ve methodları da implement edebilir. Bir üst classın field ve methodlarının alt classa kalıtım ile aktarılabilmesi için önünde private keywordünün olmaması gerekir. Bu koşulda tüm field ve methodlar alt class‘ta yazılmamış olsa bile varmış gibi kullanılabilir.
Örnek ile gidersek daha güzel olacak. Araba örneğinde bir otomobil ile bir kamyonet düşünelim. Her ikiside birer araba sayılabilir ve her ikisininde Car classında belirtilen color, speed, gear gibi özellikleri, changeColor, changeGear, speedUp gibi aksiyonları vardır. Fakat bunların dışında kamyonete özel olarak yukKapasitesi gibi bir özellik ve kasayiBosalt gibi bir aksiyon olabilir. Sadece kamyonet tipindeki class‘ların kullanacağı bu field ve methodlar kamyonet classına yazılır, diğer ortak olanlar Car classında kalır ve inheritance ile alınıp kullanılır.
- Bir class’ın alt class sayısında bir sınır yoktur. İstenildiği kadar alt class türetilebilir.
- Bir class’ın sadece tek bir üst classı olabilir. Java multi-inheritance özelliğini desteklemez.
Java’da alt class implemantation’u şu şekilde yapılır;
class Truck extends Car { //Truck spesifik field ve methodlar burada tanımlanır. }
Inheritance sayesinde alt class‘ların çok daha sade olduğuna ve okunulabilirliğinin arttığına dikkat edin. Tabi burada alt classın inheritance sayesinde sahip olduğu field ve methodların görünmemesi sorun olabilir. Dikkat etmek lazım. Örnekteki Truck classı içi boş görünse bile Car classında belirtilen field ve methodları var, ve bunları kullanabilir, buna dikkat edelim…
Interface:
Yukarıda anlattığım gibi object‘ler diğer object‘ler ile, dış dünya ile ilişkilerini methodları yardımıyla yaparlar, bu ilişkilerin nasıl olacağı methodlar ile tanımlanır. Methodlar object‘lerin dış dünya ile arayüzleridir, yani interface‘leri (: Böyle değince birşeyler anımsatmış olabilir. Dış dünyadaki arkadaşlar object‘inizi kullanacaklar, e nasıl kullanacaklar?, methodları ile, methodları nerden bilecekler?, interface ile… Şöyle düşünün, arabanız var ve kullanacaksınız. Motorun içinde neler olduğunu, kabloları, hidrolik sistemleri..vs hiçbirini bilmiyorsunuz. Araba kullanmak için bunları bilmenize gerek yok çünkü. Sadece ön tarafta sizin için tasarlanmış paneli kullanırsınız. Burada bir arayüz vardır, butonlar, pedallar, direksiyon, kollar vs… Bunların herbirini kullandığınızda (methodu çağırmak gibi) içerde birşeyler yapılır ve isteğiniz yerine getirilir. İşte bu butonların listesini yazdığımız yere interface diyoruz.
Bir interface oluştururken bu interface‘in ait olduğu classların sahip olması gereken methodları yazarız. Fakat implement etmeyiz, sadece isimlerini yazar bırakırız. Araba classı için bir interface olmuş olsa;
interface ICar { void changeColor(string newValue); void changeGear(int newValue); void speedUp(int increment); void applyBrakes(int decrement); }
şeklinde tanımlarız. Buna bakan kişi, hiç ayrıntıları görmeden, oldukça sade bir şekilde bir araba classını nasıl kullanacağını anlar. Bu methodların araba classında olacağından emin olur. Tabiki bunun için araba clasınızda bu interface’i implement ettiğinizi belirtmeniz gerekir. Onuda şöyle yaparsınız.
class Car implements ICar { // Car class'ını daha önce yazmıştık. // Interface'de belirtilen methodlar burada implement edilir. }
Bu şekilde bir interface oluşturmuş ve kullanmış oluyoruz. Böylece class‘ımız daha resmi bir hal alıyor ve implement ettiği interface‘de belirtilen methodları kullanacağının sözünü vermiş oluyor. Interface ile class arasındaki bu ilişki build-time‘da kontrol ediliyor ve söz verdiğiniz şekilde methodları tanımlamazsanız compile time ‘da hata alırsınız.
- Interface içerisinde sadece method değil, field da bulunabilir (Bknz: constants).
- Bir interface boş olabilir. Bu durumda sadece bazı classları işaretlemek için kullanılabilir.
- Bir class birden fazla interface sahibi olabilir. implements keywordünden sonra aralarına virgül konarak yazılır.
Instantiation:
Bir class yazdıktan sonra o classı kullanarak object‘ler oluşturmaya instantiation denir. Yani o class‘tan bir instance oluşturmuş olursunuz. Bunu yapmak için new keywordü kullanılır. new keywordü memory ‘de o classın bir örneğinin sığabileceği kadar alanı allocate eder ve initialization için constructor methodunu çağırır.
Constructor:
Daha önce class‘ların methodlarından bahsetmiştim. Her class için özel bir method vardır. Bu methodu diğer methodlardan ayıran özellik class ile aynı isimde olması ve hiçbir değer return etmiyor olmasıdır. Ayrıca bu method class‘dan object oluştururken yani instantiation aşamasında çağırılır. Eğer class‘ınızda bir constructor yoksa, Java Compiler implicitly hiç parametre almayan bir constructor oluşturur ve bunu çağırır. Bu constructor içerisinde de class‘ınız üst class‘ına ait boş constructor çağırılır. Eğer bir üst class yoksa Java’da build-in olarak var olan Object isimli class‘ın boş constructor’ı çağırılır (Object class‘ı çok temel bir class‘dır ve biz yazmasakda aslında her class Object class‘ından inherit eder). Burada benim önerim constructor‘larınızı yazın. Compiler’ın bu işi sizin için halletmesine izin vermeyin, aksi taktirde sorunla karşılaşabilirsiniz.
- Hiç constructor yazılmamış bir class’ın boş constructor’ı varmış gibi davranılır.
- Bir class’ın birden fazla constructor’ı olabilir (Bknz: overloading).
- Bir constructor içerisinde üst classın constructor’ı super() şeklinde çağırılır.
- Bir class’ın constructor’ı private olursa, o class’dan object üretilemez (Bknz: access modifiers).
- Bir constructor içerisinden aynı class’ın başka bir constructor’ı this() şeklinde çağırılabilir.
Initialization:
Instantiation aşamasında memory’de object için yer ayrılır ve constructor çağırılır dedik. Constructor bu alandaki field’lara başlangıç değerlerini atar, bu işleme initialization denir. Son 3 başlıkta anlattıklarımı uygulamasını yaparak göstereyim;
class Car { private string color; private int speed; private int gear; public Car () { color = "black"; speed = 0; gear = 1; //* üstteki 3 satır yerine //* this("black", 0, 1); yazılarak //* 2. constructor çağırılabilir. } public Car (string newColor, int newSpeed, int newGear) { color = newColor; speed = newSpeed; gear = newGear; } public void changeColor(string newValue) { color = newValue; } public void changeGear(int newValue) { gear = newValue; } public void speedUp(int increment) { speed = speed + increment; } public void applyBrakes(int decrement) { speed = speed - decrement; } public void printStates() { System.out.println("Color:" + color + " Speed:" + speed + " gear:" + gear); } }
Yukarıda Car class’ını tekrar yazdım. Şimdi bu classın objelerini oluşturalım;
public class CarFactory { public static void main(String[] args) { //* c1 ve c2 isimli 2 araba object'i oluşturalım Car c1 = new Car(); Car c2 = new Car("white", 80, 2); } }
Buradaki kodun Car c1 kısmı decleration olarak bilinir ve herhangi bir primitive variable oluşturmaya benzer. Örneğin int sayi = 5; derken yaptığınız gibi objectinizi koymak için bir değişken oluşturursunuz. Fakat burada bir fark var, bu değişken (c1 ve c2) object’in kendisini tutmaz, object’in bulunduğu memory alanına referans eder, yani bir pointer’dır. new ile alanı ayırırsınız, alanın adresini değişkene atarsınız, constructor ile de initialize edersiniz.