OOP: Soyutlama

OOP’nin 3 temel özelliğinden önceki yazılarımda bahsetmiştim. Bu yazımda ise soyutlama hakkında bilgi vereceğim. Soyutlama kelimesini duyunca “Zaten yazdığımız sınıflar hep soyut değil mi?” diye sorabilirsiniz. Aslında duruma bu şekilde bakmamak lazım. Nesneler, 3 boyutlu canlılar gibidir. Doğarlar; türetilirler, yaşarlar; belirli sorumluluklarını yerine getirirler  ve ölürler; nesne için ayrılan hafıza işletim sistemine bırakılır. Soyut sınıflar ise nesneleri doğrudan üretilemeyen sınıflardır. Örnek olarak aşağıdaki sınıfı inceleyebiliriz.

Java’da soyut sınıflar abstract anahtar kelimesi ile tanımlanır. Örneğin Shape shape = new Shape() ile nesne oluşturulamaz. Soyut sınıflar aynı zamanda soyut metodlar içerebilir. Soyut metodlar İngilizce olarak söylersem implement edilmeyen metodlardır. Yani metod sadece tanımlanır fakat metodun içeriği, gövdesi yazılmaz. Bu sayede esnek yapılar oluşturabiliriz. Örnek olarak Shape sınıfı iki boyutlu şekilleri simgelesin, bu şekillerin alanlarını hesaplayan bazı metodlar ekleyelim:

}

Bu örnek aslında  Java final sınavında sorulan bir örnekti. Ben de anlatmak için bu örnekten faydalanacağım. Shape sınıfımıza baktığımızda 2 adet metod görüyoruz. calculateArea metodu şeklin alanını alıp ekrana yazıyor. getArea metodu ise soyut bir metod şeklin alanını hesaplıyor. Fakat bu metodun içeriği tanımlanmamış.

Not: Java diline soyut metodlar için yine abstract anahtar kelimesi kullanılır. Java dışında başka bir programlama dili ile ilgilenen arkadaşların o dilin kurallarını incelemesi gerekmektedir. Örneğin C++ dilinde virtual anahtar kelimesi tanımlayıp metodu 0′a eşitlemeniz gerekiyor. virtual anahtar kelimesi aynı zamanda override edilebileceğini gösterir. Java da ise final olarak tanımlanmayan metodlar varsayılan olarak override edilebilir. final anahtar kelimesinin işlevini yazının sonunda anlatacağım.

Dikkat ederseniz calculateArea metodu, getArea metodunu kullanıyor ve bu metod soyut bir metod. Mantıken düşündüğünüzde de bu sınıfın zaten doğrudan üretilmemesi gerekir. Üretilebilmesi için getArea metodunun işlevselleştirilmesi gerekir. Şimdi Rectangle(Dikdörtgen) sınıfımızı oluşturalım.

Rectangle sınıfımız Shape sınıfından kalıtım yolu ile calculateArea() ve getArea() metodlarına sahip oldu. Bir dikdörtgenin iki farklı kenarı var ve bu kenar uzunluklarını a ve b değişkenleri ile ifade ettik. getArea metodu bu iki uzunluğu çarparak geriye double olarak dönderiyor. Soyut olmayan sınıflar soyut bir sınıftan kalıtım ile genişletildiğinde ata sınıfın bütün soyut metodlarını işlevselleştirmek zorundadır. Eğer getArea sınıfını işlevselleştirmeseydik derleme anında hata alırdık. Bu arada Rectangle sınıfı eğer soyut bir sınıf olsaydı getArea() metodunu işlevselleştirmek zorunda değildik. Rectangle dan üretilen herhangi bir soyut olmayan bir sınıfa bu sorumluluğu bırakmış olurduk. Artık Shape sınıfının dolaylı olarak bir nesnesini türetebiliriz: (Shape shape = new Rectangle(3,5) ).

Örnek olarak bir başka sınıf daha ekleyelim:

Bu kez de Triangle(Üçgen) sınıfı ekledik. Yükseklik ve bir kenarını alarak alanı hesapladık. OOP’nin soyutlama özelliği esnek bir yapı sağlar. Metodları o sınıfa göre işlevselleştirebiliriz. Bu sınıfların işlevini aşağdaki sınıfı koşturursak görebiliriz.

CalculatorTest sınıfına bakarsanız calculateArea metodu Shape bir shape nesnesini parametre olarak almaktadır. Buraya dikkat edin, calculateArea metodu gelen şeklin bir üçgen mi veya dikdörtgen mi olup olmadığını bilmez. Zaten onu ilgilendirmiyor. Gönderilen şeklin alanını hesaplattırıp ekrana yazar. Fakat Shape sınıfından genişletildiği için calculateArea isimli bir metoda sahip olduğunu bilir.  Yeni bir şekil eklerseniz, getArea metodunu işlevselleştirmek zorundasınız. Shape‘i ata kabul etmenin bedeli var. Bu aslında bir anlaşmadır 😉

Interface (arayüz)

Java’da interface’ler oldukça önemlidir. Java ve diğer birçok programlama dilinde birden fazla genişletilme (extend) yapamazsınız. Ama birden fazla interface’i sınıfınıza işlevselleştirebilirsiniz (implement). Interface’ler oldukça esneklik sağlarlar. Interface’lerin bütün metodları soyut metodlardır. Örnek olarak aşağıdaki interface i inceleyelim.

UserRepository sınıfı veritabanına gereken sorguları yapıp bize istediğimiz işlemleri gerçekleştirmektedir. Baktığınızda UserRepository arayüzünün birkaç metoda sahip olduğunu görebilirsiniz. Bu metodların tamamı soyut metodlardır.  Interface’lerde soyut metod tanımlarken abstract anahtar kelimesini kullanmak zorunda değilsiniz. User sınıfımız da aşağıdaki gibi olabilir.

Diyelimki müşteri MySQL veritabanı kullanmak istedi. UserRepository arayüzünü MySQL veritabanına göre işlevselleştirelim:

Karışmaması adına sınıf içerisine herhangi sql kodu yazmadım. Gördüğünüz gibi UserRepository sınıfını işlevselleştirdik. Şimdi diyelim ki MySQL veritabanı kullanmaktan vazgeçtiniz, Postgres veritabanını tercih ettiniz veya Hibernate gibi ORM teknolojilerini kullanmak istediniz. Bunun için yapmanız gereken UserRepository arayüzünü işlevselleştirmeniz Peki avantajı ne olurdu? Örnek olarak aşağıdaki sınıfımızı inceleyelim.

UserAccountService isimli sınıfımız veritabanı üzerinde bazı işlemlerin yapmasını sağlamak zorunda olduğunu düşünelim. Gördüğünüz gibi yapılandırıcı metodu UserRepository nesnesi alıyor. Peki neden böyle yaptık? Diyelim ki veritabanını değiştirdiniz. Bu sayede sadece gereken veritabanı kodlarını yazarsınız. Bütün sisteminizde değişiklik yapmanıza gerek kalmaz. Sadece UserAccountService sınıfına yazdığınız yeni sınıfın nesnesini verirsiniz. (new UserAccountService(new  UserRepositoryPostgresImp()). UserAccountService sınıfı sadece gelen nesnenin UserRepository arayüzünün özelliklerine sahip olduğunu bilir. Hangi veritabanının veya hangi teknolojinin kullanıldığını, bağlantının nasıl sağlandığını, hangi sunucuya bağlandığı gibi bilgiler UserAccoutService tarafından bilinmez ve onu ilgilendirmez. Bu da kapsüllemenin başka bir çeşididir.

final Anahtarı

Java’da bahsetmediğim bir de final anahtar kelimesi vardır. Final anahtar kelimesi sınıflarda kullanıldığında o sınıf genişletilemez (extend), metodlarda kullanıldığında ise bu sınıfı genişleten başka bir sınıf bu metodun üzerine yeni birşey ekleyemez (Override), değişkenlerde kullanıldığında ise o değişkenin değeri sabit kalmak zorundadır.

Size elimden geldiği kadar soyutlama hakkında bilgi vermeye çalıştım. Bununla birlikte OOP yazı dizisini de bitirmiş bulunuyorum. Umarım yazdıklarım faydalı olmuştur. Kafanıza takılan sorular olursa bana bildirebilirsiniz. Belki bu sayede yazıyı daha da iyileştirebilirim.

İyi çalışmalar