Tag Archives: OOP

Basit Bir Abstraction Örneği

Object Oriented tasarım ve programlamada soyutlamanın her zaman önemini vurgulamışımdır. Yazılım geliştirirken soyutlamalar sayesinde yazılımı daha yönetilebilir parçalara, modüllere ayırıp daha esnek yazılımlar geliştirebiliyoruz. Buna geliştirdiğim yazılımdan küçük bir örnek vermek istedim.

Geçenlerde Hibernate Log dosyalarını parse eden bir program yazdığımı burada belirtmiştim.  Program belirli bir dizideki .log uzantılı dosyaları parse edip içlerinden SQL cümlelerini çıkarıyordu. Ekranda bulunan bir butonun altında yazılan kodda seçilen log dizininin altındaki log dosyalarını alıp Parser sınıfına işlemesi için gönderiyordu. Kodun ilk yazılan şekli aşağıdaki gibiydi.

private void openFileActionPerformed(java.awt.event.ActionEvent evt) {
    try {        
       //log dosyalarını seçili dizinden alıyoruz

       File directory =fc.getSelectedFile();
       FilenameFilter filter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".log");
            }
        };
        File[] files =directory.listFiles(filter);

        //*****************
 
        for (File file : files) {
            Parser parser = new Parser(file);   
            sqlLogs = parser.parse();
            for (SqlLog sqlLog : sqlLogs) {
                listModel.addElement(sqlLog);
            }
        }

    } catch (Exception ex) {
        showMessage(ex.getMessage());
    }
}

Yukarıdaki basit bir işlem fakat metodun iki işi bir arada yaptığını fark ettiniz umarım. Log dosyalarını listeleme ve bu dosyaları parse etme. Ayrıca uygulamamı düşündüğümde şöyle bir ihtiyacım olduğunu anlıyorum.Uygulamam “Log dizinindeki dosyaları parse etme” işlemini yapıyor. Bu yüzden basit ama kodu ve yönetimi arttıran yeni bir sınıf ekliyorum. Aslında kısacası soyutlama yapıyorum. Uygulamam da daha önceden geçen “Log Dizini” dediğim kavram için bir sınıf oluşturuyorum. Kodu aşağıdaki gibi değiştirdim.

public class LogDirectory {
    private File directory;

    public LogDirectory(File directory) {
        this.directory = directory;
    }

    public File[] getLogFiles() {
        FilenameFilter filter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".log");
            }
        };
        return directory.listFiles(filter);
    }
}

Yukarıdaki kodda basit bir soyutlama yaptım. Log dizinindeki dosyaları getiren işi ayrı bir sınıf olarak uygulamaya ekledim.Metod aşağıdaki hale geldi.

private void openFileActionPerformed(java.awt.event.ActionEvent evt) {
    try {       
        LogDirectory logDirectory =new LogDirectory(fc.getSelectedFile());        
        File[] files = logDirectory.getLogFiles();

        for (File file : files) {
            Parser parser = new Parser(file);
            sqlLogs = parser.parse();
            for (SqlLog sqlLog : sqlLogs) {
                listModel.addElement(sqlLog);
            }
        }

    } catch (Exception ex) {
        showMessage(ex.getMessage());
    }
}

Küçükde olsa bu tarz soyutlamanın uygulama için oldukça önemli olduğunu düşünüyorum.

Örnek Kod : Visual Studio Class, Table Generator Add-In

Visual Studio’da Add-In geliştirmeyi öğrenmek için küçük bir proje geliştireyim dedim. Projede basit olarak bir Solution Explorer’dan seçtiğiniz sınıfı Server Explorer üzerindeki veritabanına tablo olarak ekliyor.Ayrıca

Server Explorer üzerindeki eklenmiş veritabanı bağlantısı üzerinden seçtiğiniz tabloyu projenize sınıf olarak ekliyor.

Kaynak kodunu

Hit This dry ed medication matters a keeps Amazon http://www.backrentals.com/shap/cialis-free-samples.html only. Certain looking five http://www.mordellgardens.com/saha/sildenafil-citrate-tablet.html Sure moisturizering my it! Started http://www.creativetours-morocco.com/fers/viagra-from-india.html waterproof. Distribute That’s: the review http://augustasapartments.com/qhio/cialis-online-overnight-shipping went never volume “click here” hilobereans.com eyebrows active when this. Large http://augustasapartments.com/qhio/low-dose-cialis The, and seeing skin, this prescription viagra online inspected to mistake would, http://www.goprorestoration.com/ordering-viagra-online information Thank caramel total! cialis no prescription Hair only and side effects for cialis teddyromano.com managable you provided My usuage buy cialis vermontvocals.org they that usually days.

ve setup dosyalarını aşağıdan indirebilirsiniz. Proje Visual Studio üzerinde Add-In geliştirmek isteyenler için faydalı olabilir. Ayrıca projeyi küçük bir Test Driven Development ve OOP örneği olarak inceleyebilirsiniz yeni başlayanlar için faydalı olabilir..Gerçekte işinize yararsada kullanıp kodu istediğiniz gibi değiştirmekte serbestsiniz.İçerisinde bug,ve eklenmesi gereken özellik olabilir, kendiniz ekleyebilir veya benden eklememi isteyebilirsiniz :) .

Proje Visual Studio 2008 üzerinde çalışıyor. Eğer kodu kullanmak ve değiştirmek istiyorsanız bilgisayarınızda Visual Studio 2008 SDK kurulu olması gerekli.

Tablodan Sınıf Oluşturma

Adım 1 : Server Explorer’dan Çalıştığınız Veritabanı bağlantısını ekleyin ve ekledikten sonra sınıfını oluşturmak istediğiniz tabloyu seçin

TableToClassAdim1

Adım 2 : Gelen ekran üzerinden sınıfın otomatik olarak oluşturulmuş özellikleri üzerinde değişiklik yapın.Sınıf adını değiştirin, yeni özellik ekleyin,özellik silin,özellik adını güncelleyin,özellik tipini güncelleyin…

TableToClassAdim2

Sınıftan Tablo Oluşturun

Adım 1 :Solution Explorer üzerinde veritabanına tablo olarak eklemek istediğiniz sınıfın üzerinde sağ tıklayın.

ClassToTableAdim1

Adım 2: Gelen ekran üzerinden veritabanına eklenecek tablo özelliklerini otomatik olarak görüntüleyecektir. Oradan ekran üzerinde oluşturulacak tablonun primary key, allow null gibi özelliklerini değiştirin, kolon ekleyin, kolon silin, kolon adını değiştirin…

ClassToTableAdim2

Tell Don’t Ask Principle

oneRing Herşey prosedürel programlamayla başladı. Bazılarımız büyük programlar yazdı, bazılarımız ise küçük birer hesap makinası yaparak kıyısından geçiverdi :) Ama hepimiz prosedürel programlamayı çok sevdik. Programlarımızı yazarken güzel güzel yapılar(struct,record),veri tipleri(int,double,char…) kullanırdık, yapılarımızı,verilerimizi metodlarımıza parametre olarak gönderirdik onlarda gereken işi güzelce yapardı.Tabi sonradan yazdığımız programlar 100.000 satırlar boyutuna ulaşmaya başlayınca o masum masum kod dosyalarımızın üst tarafında tanımlı yapılar bize düşman görünmeye, en az 5 tane parametre alan fonksiyonlarımız da yönetilmeme ye ve pekde hoş gözükmemeye başladı. Ardından kulaklarımıza Mordor diyarından şu nağmeler gelmeye başladı. (Yüzüklerin efendisini çok sevdiğim belli oluyor herhalde:) )

One Object to rule them all, One Object to find them,
One Object to bring them all and in the Class bind them

Nağmelerde, hepsini ; yani veri ve fonksiyonları bir araya getirecek, ondan sonra onları Class(sınıf) içerisinde birleştirecek bir güç olan nesneden bahsediyordu. Hepimiz bu nağmelerin içerisnde olan nesnelerin büyüsüne kapıldık ve o aşamadan sonra kendimizi Object Oriented rüzgarının önünde buluverdik.  Artık hepimiz bu gücü kullanıp daha yönetilebilir, okunabilir, esnek yazılımlar geliştirmek için can atıyorduk. Fakat prosedürel programlamayı o kadar çok sevmiştik ki eski alışkanlıklarımızdan bir türlü vazgeçemiyorduk. İlk aşklar unutulmaz derler herhalde ondan olsa gerek :) Artık benim PObject Oriented olarak adlandırdığım nesneler ile prosedürel programlama yapıyorduk.

Her ikisinin sevdiğimiz özelliklerini kullanıp güzel güzel programlar yazmaya başlamıştık. Verileri bu sefer struct,record gibi yapılar içerisine değilde sınıflar içerisine koymaya başladık.Fonksiyonlarımızıda uygun bir yer bulabiliyorsak oraya değilse sağa,sola yerleştiriyorduk. Fakat yine işler projeler büyüdükçe çığırından çıkmaya başladı. Projeler büyüdükçe, değişiklikler yapılmaya başladıkça işler oldukça zahmetli olmaya başladı. Küçük bir talepteki değişiklik uygulamanın birçok yerinde değişiklik yapmamızı gerektiriyor ve bu değişiklik sonucunda ortaya çıkan hatalarla baş edememeye başlıyorduk. Aslında biraz da kafamız karışmıştı. Nağmelerde duyduğumuz o güçlü nesneye yönelik programlama aslında pekde güçlü olmadığını düşünüyorduk.

Kaptırmışım kendimi yüzüklerin efendisi falan araya girince hikayeyi fazla uzattım kusura bakmayın :) Aslında problem bu noktada geliştirdiğimiz uygulamada iş mantığının nesnelerin içinde değilde uygulamanın geneli içine dağılmış olması. Yani en temel nesneye yönelik programlama prensibi verilerin ve verileri işleyen metodların bir arada bulunması gerçekleşmediğinden dolayı uygulama geneline artan Bağımlılık(Dependency,High Coupling) sorunlarının yol açtığı problemlerdir.

Bu sorunların önüne geçebilmek ve sınıflara sorumluluğu daha düzgün atayıp, iş mantığını gerçekten olması gereken yere koymak için kullanılan prensiplerden biride Tell Don’t Ask Principle yani “Sorma, söyle” prensibidir.

Procedural code gets information then makes decisions. Object-oriented code tells objects to do things.
— Alec Sharp

Alec Sharp’ın dediği gibi prosedürel kod veriyi alır ve o veri üzerinden kararlar verir. Object oriented kod ise nesnelere yapmaları gereken şeyleri söyler. Bunu meşhur gazeteci çocuk, cüzdan problemi üzerinde örneklerle inceleyelim. İlk olarak nerede okumuştum hatırlamıyorum ama bu prensibi anlamama kod ve kavramsal olarak oldukça yardımcı olmuştu. Aynı örnek üzerinden devam edelim.

Örnek

Kodları buradan indirebilirsiniz : TellDontAsk.rar

Gazeteci arkadaşımız her sabah bisikletine atlayıp ev ev dolaşıp gazete dağıtıyor. Haftalıkta müşterilerinden gazete paralarını topluyor.Para toplayacağı müşterilerin listesi elinde alıp kapı kapı dolaşıyor ve ücretleri alıyor. Ardından topladığı hasılatı patronuna teslim ediyor.İlk olarak programımızı şu şekilde yazalım.

public class Cuzdan {
    private double para;

    Cuzdan(double para) {
        this.para =para;
    }

    public double getPara() {
        return para;
    }

    public void setPara(double para) {
        this.para = para;
    }  
}
public class Gazeteci {
    private double hasilat =0;
    public void odemeAl(Musteri musteri,double miktar){
        if(musteri.getCuzdan().getPara()<miktar){
            throw new RuntimeException("Cüzdanki para yeterli değil.");
        }else{
            musteri.getCuzdan().setPara(musteri.getCuzdan().getPara()-miktar);
            hasilat +=miktar;
        }        
    }

    public double getHasilat() {
        return hasilat;
    }
}
public class Musteri {
    private String adi;
    private Cuzdan cuzdan;

    public Musteri(String adi,double para) {
        this.cuzdan =new Cuzdan(para);
        this.adi =adi;
    }    

    public Cuzdan getCuzdan() {
        return cuzdan;
    }

    public String getAdi() {
        return adi;
    }
}
public class Main {
    public static void main(String[] args) {
        Musteri ahmetAmca =new Musteri("Ahmet",100);
        Musteri bakkalMehmet =new Musteri("Mehmet",50);
        Musteri kasapAli =new Musteri("Ali",80);
        
        List<Musteri> musteriler =new ArrayList<Musteri>();
        musteriler.add(ahmetAmca);
        musteriler.add(bakkalMehmet);
        musteriler.add(kasapAli);
        
        Gazeteci cihat =new Gazeteci();
        
        for(int i=0; i<musteriler.size(); i++){
            Musteri musteri =musteriler.get(i);
            try{
                cihat.odemeAl(musteri, 60);
            }
            catch(Exception ex){
                System.out.println("Tahsilat yapılamadı! Müşteri adı : "+musteri.getAdi());
            }
        }        
        System.out.println("Toplanan hasılat miktarı : "+cihat.getHasilat());
    }
}

Prosedürel stili o kadar çok seviyoruz ki muhtemelen yukarıdaki kodda pek bir problem göremeyeceğiz.Yanılmıyorum değilmi?Çünkü ilk başlarda bu tarz kodlar bana süper Object Oriented kodlar gibi geliyordu ve açıkçası biri bana o zaman bu kodda problem var dese gülerdim :)

Probleme teknik olarak değilde gerçek hayat problemi olarak bakalım. Evinize gelen gazeteciye cüzdanınızı verip içerisinden ne kadar lazımsa al kardeş dermisiniz? Eğer öyleyse sizin gazeteciniz olmayı isterdim :) Çoğumuzun cüzdanını vermeyeceğini düşünüyorum açıkcası.Gazeteciye “kardeş ne kadar lazım söyle” diye sorup ona göre parayı cüzdanımızdan çıkarıp öyle veririz. Bu koddaki problemde bu. Gazeteciye aslında cüzdan nesnesi ile cüzdanımızı veriyoruz gazeteci önce bi içerisine bakıyor gerekli para varmı ardından varsa içerisinden gereken parayı alıyor.

Tell Don’t Ask(sorma, söyle) presibinin belirttiği gibi aslında biz burada Musteri ve Cuzdan nesnelerine surekli sorular soruyoruz ve ardından sorduğumuz soruların cevabına göre bazı işlemleri o nesneler yerine biz yapıyoruz. Musteriye sordugumuz soru getCuzdan(), Cuzdan nesnesine sordugumuz soru ise getMiktar() metodları.Bu yüzden Musteri nesnesinin iç yapısını öğrenmiş oluyoruz.Mesela müşterinin parayı cüzdanında taşıdığını biliyoruz. Ya müşteri bize parasını evdeki kasasından vermek isterse?Gazeteciye tutup “Al kasayı kardeş içerisinden ne kadar lazımsa o kadar al ” mı diyeceğiz? :) Dolayısıyla nesneler arasındaki bağımlılık(coupling) artıyor. Uygulamada bu şekilde müşteri nesnesinin içerisindeki alanlara soru sorup ona göre karar verip çeşitli işlem yaptıran sınıflar ne kadar artarsa müşterinin iç yapısını değiştirmemiz o kadar zorlaşıyor. Örnek olarak müşterinin artık parayı Cüzdan’dan değilde kredi kartından vereceğini düşünelim. Hangi sınıfları değiştirmemiz gerekir onlara bakalım.

Şimdi yeni sınıfımız Kredi kartına göre sınıflarımızın düzeltilmiş halini yazalım. Bakalım bir sınıfdaki değişiklik yüzünden kaç sınıfı değiştirmek zorunda kalacağız.Ana sınıf değişmediği için onu yazmıyorum. Diğer değişen sınıfları yeni halleri ile aşağıya yazıyorum.

public class Gazeteci {
    private double hasilat =0;
    public void odemeAl(Musteri musteri,double miktar){
        if(musteri.getKrediKarti().getLimit()<miktar){
            throw new RuntimeException("Cüzdanki para yeterli değil.");
        }else{
            musteri.getKrediKarti().setLimit(musteri.getKrediKarti().getLimit()-miktar);
            hasilat +=miktar;
        }        
    }

    public double getHasilat() {
        return hasilat;
    }
}
public class Musteri {
    private String adi;
    private KrediKarti krediKarti;

    public Musteri(String adi,double para) {
        this.krediKarti =new KrediKarti();
        this.adi =adi;
    }    

    public KrediKarti getKrediKarti() {
        return krediKarti;
    }

    public String getAdi() {
        return adi;
    }
}
public class KrediKarti {
    private double limit =1000;

    KrediKarti() {
        
    }

    public double getLimit() {
        return limit;
    }

    public void setLimit(double limit) {
        this.limit = limit;
    }  
}

Gördüğünüz gibi Main sınıfı hariç bütün sınıfları basit bir değişiklik yüzünden değiştirmek zorunda kaldık. Burada aslında sadece Müşteri yani cüzdana sahip sınıfın etkilenmesi gerekirdi fakat prosedürel stilde sürekli nesnelerin iç yapısını sorgulayarak ona karar veren kodumuz yüzünden Gazeteci sınıfıda bu değişiklikten etkilendi.

Peki bu problemi nasıl düzeltebiliriz ? Tell Don’t Ask yani işimizi nesnelere soru sorararak değilde onlara ne yapması gerektiğini söyleyerek yapabiliriz. Aslında bu yeni birşey değil OOP’nin en temel kuralını kullanıyor alıyor yani Encapsulation. Nesnelerin iç yapısını çok fazla dışarı açtığımızda kendi üzerine düşen işleri başkaları yaptığında bu tarz problemler kaçınılmaz. Bu küçük bir örnek belki çok fazla problem olmaz fakat büyük bir uygulamada bu tarz iç yapısını dışarı açan bir nesnede değişiklik olduğunu düşünün? Belkide yüzlerce sınıfı değiştirmek,hatalarını düzeltmek,test etmek zorunda kalacaksınız. Şimdi bu kodu Tell Don’t ask prensibine uygun olarak aşağıdaki gibi yazalım.

public class Cuzdan {
    private double para;

    Cuzdan(double para) {
        this.para =para;
    }

    double cek(double fiyat) {
        if(fiyat>para)
          throw new RuntimeException("Cüzdanki para yeterli değil!");
        else{
            para =para-fiyat;
            return fiyat;
        }
    }
}
public class Musteri {
    private String adi;
    private Cuzdan cuzdan;

    public Musteri(String adi,double para) {
        this.cuzdan =new Cuzdan(para);
        this.adi =adi;
    }    
    
    public double odemeYap(double fiyat){
        return cuzdan.ver(fiyat);
    }

    public String getAdi() {
        return adi;
    }
}
public class Gazeteci {
    private double hasilat =0;
    public void odemeAl(Musteri musteri,double miktar){
        hasilat +=musteri.odemeYap(miktar);     
    }

    public double getHasilat() {
        return hasilat;
    }
}

Şimdi Tell Don’t Ask prensibine göre ile yazılmış yeni kodumuzu inceleyelim. Gazeteci sınıfına bakın artık müşterinin ne cüzdnından haberi var nede kredi kartından. Ona sadece yapması gereken şeyi söylüyor : “Bana şu kadar ödeme yap”. Müşteri nesneside aynı şeyi yapıyor cüzdana bana şu kadar para ver diyor. Ardından cüzdan da kendi içerisinde olan parayı istenen miktarla karşılaştırıyor ve ona göre veri parayı veriyor. Yani herkes yapması gereken işi yapıyor. Artık cüzdan nesnesi içerisinde sadece veri bulunan bir aptal sınıf yada Anemic Domain Model değil, onu nasıl işleyeceğini bilen bir sınıf.

Şimdi bu yapıda yazılmış bir kod üzerinde Cuzdan yerine paramızı kredi kartı üzerinden ödemek için değişmesi gereken sınıfları tekrar yazalım.

public class Musteri {
    private String adi;
    private KrediKarti krediKarti;

    public Musteri(String adi,double para) {
        this.krediKarti =new KrediKarti();
        this.adi =adi;
    }    
    
    public double odemeYap(double fiyat){
        return krediKarti.cek(fiyat);
    }

    public String getAdi() {
        return adi;
    }
}
public class KrediKarti {
    private double limit =1000;

    KrediKarti() {
        
    } 
    
    double cek(double fiyat) {
        if(fiyat>limit)
          throw new RuntimeException("Limit yeterli değil!");
        else{
            limit =limit-fiyat;
            return fiyat;
        }
    }
}
public class Gazeteci {
    private double hasilat =0;
    public void odemeAl(Musteri musteri,double miktar){
        hasilat +=musteri.odemeYap(miktar);     
    }

    public double getHasilat() {
        return hasilat;
    }
}

Gazeteci sınıfında yukarıda gördüğünüz gibi hiçbir değişiklik yapmadık. Yani sadece iç yapısını değiştirdiğimiz sınıflar etkilendi. O da müşteri sınıfı ama diğer şekilde hem gazeteci hemde müşteri sınıfı etkileniyordu.

Tabi bunun dışında gördüğünüz gibi kodun okunulabilirliği ve yönetimi oldukça kolaylaştı. Çünkü herkes kendi sorumluluğunu yerine getiriyor, nesneler kendi içerisindeki veriler üzerinde işlem yapıyor. Bu şekilde uygulamada değişiklik sadece gereken yerde yapılmış oluyor. Ama diğer şekilde iş mantığı uygulamanın bütün yerine dağılmış olduğu için değişiklik sonucunda birsürü yerde değişiklik yapmamız gerekiyor. Buda hata riskini ve değişim maliyetini oldukça arttırıyor.

İlk başlarda bu prensibi okuduğumda oldukça şaşırmıştım ve açıkçası nesneye yönelik programlamanın en temel ilkesini o zaman anlamıştım. Veri ve veri üzerinde işlem yapan metodların bir arada olması. Prosedürel mantığa o kadar alışmışızki nesneye yönelik bir programlama dilinde onun etkisinden hala kurtulamıyoruz. Bu prensip anlaması ve alışması belkide en zor temel nesneye yönelik programlama prensiplerinden birisi. Ben bile hala bazı yerlerde içgüdüsel olarak nesnelere soru sorup bazı işleri yaptırmaktan kendimi alıkoyamıyorum. Ama sürekli kodu gözden geçirip “Acaba bu işi bu sınıf mı yapmalı?, Bu işin ait olduğu yer burası mı?” diye sorgularsanız sorumluluğun gerçekten ait olduğu yeri belirleyebilirsiniz. Eğer nesnelerinizde sadece getter,setter(java) yada property(C#) bulunuyorsa nesnelerinizden şüphelenmenizin ve tekrar gözden geçirmenizin vakti gelmiştir.

Ortak Düşmanımız : Bağımlılık (Dependency,High Coupling)

domino1

Bu yazımızda yazılım geliştirirken karşımıza çıkan en büyük problemlerden biri olan bağımlılık (Dependency,Coupling) konularına değineceğim. Neden az olanı makbüldür onu anlatmaya çalışacağım.

Yazılım geliştirirken gerçek dünyadaki gibi bağımlılık (Dependency,Coupling) başımıza çoğu zaman dert açıyor.Bağlanılması zararlı bir şeye bağlandınızmı nasıl ondan kurtulmak onsuz birşey yapmak imkansız oluyorsa aynısı yazılım geliştirirkende geçerli oluyor. Yazılımda bağımlılığı tanımyacak olursak iki metodun,sınıfın,modülün bir işi gerçekleştirmek için birbirine ihtiyaç duymaları kısacası birbirlerini kullanmalarıdır diyebiliriz.

Peki bağımlılığın ne gibi zararları var neden düşman ilan ettik birazda ondan bahsedelim. Öncelikle iki sınıfın birbirine bağımlı olmasının en büyük problemlerinden biri herhangi birindeki değişikliğin diğerini etkilemesidir bunu yandaki domino taşlarına benzetebilirsiniz. Herhangi bir yerden taşın birini düşürdüğünüz zaman birbiri ardına olan bütün taşlar devrilmeye başlar. Aynı şekilde geliştirdiğimiz yazılımda sınıflar,modüller… arasındaki bağımlılık ne kadar fazla ise domino taşı gibi herhangi birinde meydana çıkan hataların,değişikliklerin bağımlı olan diğer sınıfları etkileme olasılığı o kadar artıyor.

Üstadlardan Robert Martin amcamızın internet üzerinde dinlediğim The Principles Of Agile Design konferansında nesneye yönelik tasarım(Object Oriented Design) nedir diye izleyicilere bir soru yöneltiyordu. İzleyiciler “Gerçek dünyayı modelleme”,”Kavramların ayrılması” .. gibi değişik cevaplar verdi. Fakat Robert Martin kısa ve öz olarak nesneye yönelik tasarımı şu şekilde özetledi “Bağımlılığı yönetmek(managing dependency)”. Gerçekten çok yerinde bir tespit nesneye yönelik tasarımın başarısı için uygulamada bağımlılığın iyi yönetilmesi ve mümkün olduğunca az olması gerekiyor. 

Aslında bağımlılığı hem kod üzerinde, hemde projeler üzerinde izlemek ve anlamak çok kolay. Bunun için JDepend,NDepend gibi birçok gelişmiş olsada basit olarak bağımlılığı ölçmek için iki yöntem kullanabilirsiniz. Mesela aşağıdaki gibi bir koda bakalım ve nelere bağımlı gözden geçirelim.

		private void tbiMesajiGonder_Click(object sender, System.EventArgs e)
		{
			Database.VeriTabani clsDatabase=new SMSNET.Database.VeriTabani();
			string strSQLTel="SELECT ID,Adi+' '+Soyadi AS AdSoyadi,GSM FROM Kisiler WHERE ID IN ("+strMesajGonderilecekIDs+")";
			DataTable dtTel=clsDatabase.GetDataTable("Telefon",strSQLTel);
			PosterClass clsPosterClass=new PosterClass();
			string strSQLParametre="SELECT * FROM Parametreler WHERE ID IN (1,2,3,4) ORDER BY ID";
			DataTable dtParametreler=clsDatabase.GetDataTable("Parametreler",strSQLParametre);

			string strFirmCode="",strUserName="",strPassword="",strOriginator="";
			string strErrDefinition="";
			int iErrCode=0;
			string strMSGID="";

			if(dtParametreler.Rows.Count==4)
			{
				strFirmCode=dtParametreler.Rows[0]["Degeri"].ToString();
				strUserName=dtParametreler.Rows[1]["Degeri"].ToString();
				strPassword=dtParametreler.Rows[2]["Degeri"].ToString();
				strOriginator=dtParametreler.Rows[3]["Degeri"].ToString();
			}
			else
			{
				MessageBox.Show("Sistem SMS gönderebilmek için gerekli parametrelere ulaşamadı. Lütfen Sistem Tanımlarınızı kontrol ediniz...");
			}

			int k=0;

			for(int i=0;i<dtTel.Rows.Count;i++)
			{
				if(dtTel.Rows[i]["GSM"].ToString()!=""&&dtTel.Rows[i]["GSM"].ToString()!=null)
				{
					clsPosterClass.AddToSmsBasket(tbSMSMesaji.Text,dtTel.Rows[i]["GSM"].ToString());
					k++;
					if(k>100)
					{
						strMSGID=clsPosterClass.sendSms(strFirmCode,strUserName,strPassword,strOriginator,null,ref iErrCode,ref strErrDefinition);
						clsPosterClass.ClearSmsBasket();
					}					
				}
			}
			strMSGID=clsPosterClass.sendSms(strFirmCode,strUserName,strPassword,strOriginator,null,ref iErrCode,ref strErrDefinition);
			clsPosterClass.ClearSmsBasket();
		}

Şimdi yukarıdaki kod baktığımızda pek de iyi şeyler görmediğimiz belli oluyor galiba. Kodu yaklaşık 4 sene önce ben yazmama rağmen pek birşey anlamıyorum açıkcası :) Muhakkak yukarıdaki kodda birsürü problem var fakat diğer problemlerinde büyük oranda sebebi olan en büyük problemlerden birisi kodun birçok sınıfa bağımlı olması(High Coupling).

Şimdi kodun nelere bağımlı olduğuna bakalım.Dediğim gibi bunu anlamanın çok basit bir yolu var bu da kodun çalışması için gerekli olan modüllerin,sınıfların, metodların sayılması. Bunu yapabilmek için öncelikle kodun Import kısmına bakmanızı tavsiye ederim. Import kısmında olan modüllere zaten direk olarak bağımlıdır. Projeler arasındaki bağımlılığı ölçmek içinde referans verdiği Dll,Jar gibi kütüphaneleri sayarak anlayabilirsiniz.Sınıflara olan bağımlılığıda o sınıf çıktığında kod çalışmaya devam edecek mi oradan anlayabilirsiniz.

  • Yukarıdaki koda baktığımızda ilk satırlarda Database nesnesi üzerinden SMS atılacak kişilerin SQL ile alındığını görüyorsunuz.Kodumuz Database’e bağımlı.
  • DataTable nesnesi üzerinden Telefonlar alınıyor. Kodumuz DataTable nesnesine yani arkaplanda olan ADO.NET teknolojisine bağımlı
  • 3. parti PosterClass nesnesi üzerinden SMS gönderimi yapılıyor. Yani PorterClass nesnesine ve bunu satın aldığımız firmaya bağımlıyız.

Şimdi bu bağımlılığın yol açtığı problemler neler onlara bakalım.

  • SMS sistemimiz sadece veritabanı üzerinden çalışacak. Mesela oldukça yaygın olan XML üzerinden aldığımız kayıtlara SMS göndermek istediğimizde bunu kodu değiştirmeden yapamayacağız.
  • Kodumuz DataTable yani Microsoft’ın sık sık değiştirmeyi sevdiği veri erişim katmanı olan ADO.NET alt yapısına bağlı. Mesela LINQ to SQL işimizi baya kolaylaştırırdı fakat onuda kodumuzu değiştirmeden kullanamayacağız.
  • Bir firmadan satın aldığımız PosterClass üzerinden SMS gönderiyoruz. Bunda bir problem yok fakat yarın birgün firma bu kütüphaneye destek vermediğinde ya da daha iyi alternatifler çıktığında yine değişik alternatifleri kullanmak hiçde kolay olmayacak.

Yukarıdaki kodda modüller, sınıflar arasındaki bağımlılığı gördük. Fakat bağımlılık bu kadarla bitmiyor açıkcası. Birazda daha sinsi olan metodlar,alanlar üzerindeki bağımlılığa bakalım. Aşağıdaki kodu inceleyelim

    public class Field
    {
        private string name;
        private bool allowNulls;
        private bool isPrimaryKey;
        public string DataType { get; set; }        

        public Field(string name, string dataType)
        {
            this.name = name;
            DataType = dataType;
            IsPrimaryKey = false;
            AllowNulls = true;
        }
        
        public bool IsPrimaryKey
        {
            get { return isPrimaryKey; }
            set {isPrimaryKey = value;}
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        
        public bool AllowNulls
        {
            get { return allowNulls; }
            set { allowNulls = value; }
        }
    }

class Program
{
    public void SaveFields()
    {
        IList<Field> fields=new List<Field>();
        for (int i = 0; i < fields.Count; i++)
        {
            Field field = fields[i];
            string sqlString = GetFieldAsSQLString(field);
            //....
        }
    }

    public string GetFieldAsSQLString(Field field)
    {
        string sqlString = field.Name + " " + field.DataType;
        if (field.IsPrimaryKey)
            sqlString += " PRIMARY KEY";
        if (field.AllowNulls)
            sqlString += " NULL";
        else
            sqlString += " NOT NULL";
        return sqlString;
    }
}

Yukarıdaki koda baktığımızda ilk başta çok fazla problem görmeyebilirsiniz fakat Field sınıfının içerisindeki alanların kodun çoğu yerinde bu şekilde kullanıldığında kodumuz bu sefer de Field sınıfının alanlarına bağımlı oluyor. Mesela GetFieldAsSQLString metoduna baktığımızda Field sınıfının alanları üzerinde bazı işlemler yapıp bir string oluşturuyor. Şunu düşünün DataType alanı artık string olarak değilde bir sınıf olarak tutacağımızı düşünün. Kodumuzun çoğu yeri GetFieldAsSQLString metodu gibi Field sınıfının alanlarını bu şekilde kullanıyorsa DataType alanını değiştirdiğimizde birçok yerde hata verecek ve kodun çoğu değişmek zorunda kalacak.

Kısaca bağımlılığın zararlarını sıralayacak olursak :

  • Üzerinde çalışan yazılım geliştiricilerin,özellikle benim gibi şuanda şanssız kesimden olan yani kodu kendi yazmayıp yönetmek zorunda olanların sistemin bütününü anlamak zorunda olmalarıdır. Çünkü bütün sınıflar,modüller birbirine bağımlıdır.
  • Yapmak zorunda olduğunuz değişikliklerin birçok sınıfı etkilemesi dolayısıyla daha uzun sürmesi vede ne kadar süreceğinin tahmin edilememesi. Herhalde ne demek istediğimi anladınız. Proje yöneticiniz gelip size bu değişiklik ne kadar sürer dediğinde çoğu zaman “Biryerde bir problem çıkmazsa şu kadar sürer..” dediğinizi hatırlıyorsunuzdur.Çoğu zamanda eğer bağımlılık fazla ise biryerde birşeyler çıkıp işinizi uzatacığına emin olun :)
  • Değişen teknolojiye adaptasyon,farklı alternatifler arasında geçiş hiçte kolay olmayacaktır. Çünkü kodun heryeri o teknolojiye bulaşmış durumdadır.

Nesnelerin birbirleri ile iletişim halinde görevlerini yerine getirmelerinden dolayı sıfır bağımlılık genelde mümkün olmasada bağımlılığın az olması çoğu durumda daha makbüldür.Bu yüzden bağımlılığı iyi bir şekilde yönetmek hem kalite yönünden,hemde zaman bakımından bize oldukça kazanç sağlayacaktır.

Peki bunları niye yazdım? :) Aşırı bağımlılığın çoğu durumda kötü olduğunu size ispatlayabilmek için :) Umarım sizinde kafanıza yatmıştır. Yukarıdaki kodların bağımlılığı azaltılmış şekilde nasıl yazarıza şuanda değinmeyeceğim çünkü  yazdığım ve yazacağım birçok konu bununla alakalı aslında. Dependency Inversion,Dependency Injection… birçoğunun adını duymuştursunuz. Bundan sonraki birkaç yazımda bağımlılığı azaltan, kodun kalitesini arttıran işlerimizi kolaylaştıran birkaç yönteme değineceğim.O yüzden bu kodlar işimize yarıyacak gibi :)

Composition Over Inheritance (Kalıtım yerine Birleşim)

Uzun zamandır işlerin yoğunluğundan dolayı yazamıyordum ama telafisi olarak düşündüğüm Object Oriented programlama açısından hayati konulara değinerek kendimi affettireceğimi umuyorum :)

Şimdi neden önemlidir ondan biraz bahsedelim. Öncelikle Object Oriented programlama dillerinde kodun tekrar kullanımını(code reuse) iki şekilde sağlayabilirsiniz. Birincisi inheritance(kalıtım) ikincisi ise Composition(güzel bir türkçe isim bulana kadar “birleşim” :) ) yöntemidir. Şimdi bu iki kullanımın neler olduğuna bakalım. Önce UML sınıf diyagramı olarak aralarındaki farka bakalım.
Devamını Burudan Okuyabilirsiniz

OOP’ye farklı bir bakış

“Neden OOP ?” sorusu bana TRT’de bayan spikerin Cem Yılmaz’a yönelttiği “Neden mizah?” sorusunu anımsatır.Teknik olarak bu sorununun cevabına Cihat daha önceki yazılarında değindiği için tekrar etmemize gerek yok.Aslında biraz da insana bakan tarafıyla OOP’yi iredelememiz gerekiyor diye düşünüyorum.

İnsanoğlu hayatını kolaylaştıracak tüm araç gereci doğadan esinlerek icat etmiştir.Kuşlardan uçak,Yusufçuk sineğinden helikopter vs.. Yani insanoğlu çevresini modelleyerek sorunlarına çözüm bulmuştur.İnsan bir proje gerçekleştirirken de iş adımlarını modelleyerek plan program oluşturur.Modellemeler gerçeğe ne kadar yakın olursa plan programlar o kadar tutarlı sonuçlar da o derece başarılı oluyor.Bana göre OOP’nin de yaptığı tam olarak, yazılımda yapılan modellemelerin gerçeğe en yakın şekilde yapılmasını sağlaması…Her nesnenin her varlığın tam olarak modelini oluşturup varlıkların kendi aralarındaki ilişkileri net bir şekilde belirlemek…

Model

View Presenter yaklaşımı ile ilgili tek başıma bir saattir içinden çıkamadığım meseleyi az önce Cihatla konuşurken -aslında sorumu tam olarak bitirmeden- çözüverdik.Bunu da şu şekilde açıklayabiliriz diye düşünüyorum:”Hangi sınıf nerden türeyecek,burada interface kullanmalı mıydım?” gibi teknik sorularla zihnimi meşgul ederken muhatabıma ne ile uğraştığımı izah etmek için olayın aslını teknik detaylara girmeden anlattım.Aradan teknik detaylar çekilince çözüm bütün yalınlığıyla ortaya çıkmış oldu.Şunu farkettimki çözmeye çalıştığımız sorunun gerçek hayatta karşımıza çıkan yazılım ile ilgili olmayan diğer sorunlardan modelleme açısından hiç bir farkı yok.

OOP’nin, insanların gündelik hayatında zaten uyguladığı bir yöntem olduğunu düşünüyorum.Bu yüzden de OOP ile yazılım insanlara çok daha sevecen ve tanıdık geliyor.Bu yüzden OOP ile yazılımda çıkan sorunlarımızı çok daha kolay hallediyoruz.Çünkü çözümü bulmak için gündelik hayatımızdaki sıradan iş akışlarına bakmamız yeterli..

Aklıma gelmişken güzel ülkemizde OOP ile yazılımı anlatırken seçtiğimiz örnekler neden hep MAASHESAPLA metodu üzerinde döner? Sosyolojik olarak incelenmesi gereken bir konudur bu.:)

YAHYA KOÇ

Sınıf Tasarımında Kontrol Edilecekler

Bu günlerde iki kere okunması gereken kitaplardan Code Complete 2‘yi tekrar gözden geçiriyorum.Kitabın Chapter 6.6’da bulunan kaliteli sınıf tasarımı için kontrol edilmesi gerekenler başlığı altında yazdıklarını buraya yazmadan geçemedim. Sınıflarımızı tasarlarken aşağıdaki maddelere dikkat etmemiz sınıfların ve yazılımın kalitesini oldukça arttıracaktır. Türkçeye çevirerek yazıyorum.Aşağıdaki listede sınıfın arayüzü(class interface) kavramını java ya da C# daki interface kavramları ile karıştırmayın. Sınıfın arayüzü derken dışarıya kullanıma açtığı metodları kast edilir.Mesela bir sınıfın 4 metodu varsa bunlardan 3’ü private 1’i public ise sınıfın arayüzünü bu dışarıya açılan public metod temsil eder.

Soyut Veri Tipleri

    Programınızdaki soyut veri tiplerinin neler olduğunu düşünüp bunların kullanım açısından arayüzlerini değerlendirdiniz mi?

Soyutlama

  • Sınıfın belirli bir amacı var mı?
  • Sınıf iyi isimlendirilmiş mi ve ismi asıl amacını tanımlıyor mu?
  • Sınıfın kullanım arayüzü iyi bir soyutlama sunuyor mu?
  • Sınıfın arayüzü onun nasıl kullanılacağını belli ediyor mu?
  • Sınıfın arayüzüne baktığınızda içinin nasıl implemente edildiğini düşündürmeyecek kadar iyi soyutlanmış mı?Sınıfı bir kara kutu(black box) olarak düşünebilirmisiniz?
  • Sınıflar kendi iç yapılarını diğer sınıfların ulaşmasına izin vermeyecek şekilde iyi koruyor mu?
  • Sınıflardan amaçları ile alakalı olmayan bilgiler çıkartılmış mı?
  • Sınıfı başka sınıflar,bileşenlere ayırmayı düşündünüz mü?Sınıfları sadece tek sorumluluğu yerine getirecek kadar fazla ayırdınız mı?
  • Sınıfın içini değiştirirken sınıf arayüzünün değişmemesine özen gösteriyormusunuz?

Encapsulation

  • Kalıtım(inheritance) sadece IS-A(dır,dir..) ilişkileri için kullanılıyor mu?Kalıtım yapan sınıflar Liskov Substitution Principle’a uyuyormu?
  • Sınıf dökümantasyonu kalıtımın nasıl yapılacağını tanımlıyor mu?
  • Diğer sınıflardan kalıtım yapan sınıflar üzerine yazılamayacak metodların(non overridable) üzerine yazmaktan kaçınıyor mu?
  • Kalıtım yapısı fazla derin değil mi?(Derin olmaması daha iyi)
  • Kalıtım yapılan temel sınıflardaki bütün field lar private mı?

Diğer Konular

  • Sınıf en fazla 7 veya daha az field içeriyor mu?
  • Sınıf diğer sınıfların metodlarını minumum sayıda çağırıyor mu?
  • Sınıf diğer sınıflar ile sadece gerçekten gerektiği anda iletişime geçiyormu?
  • Sınıfın bütün elemanları yapıcı metodlarda oluşturuluyormu?

Aslında her madde ayrı ayrı bir yazı başlığı altında incelenebilir yeri geldikçe bunlar ile alakalı yazmaya çalışacağım. Fakat liste iyi sınıf tasarımı için yapılması gereken çoğu şeyi içeriyor. Sınıflarınızı listeyi kontrol ederek tekrar gözden geçirmenizde fayda var.

Interface mi, Abstract mı?

Genellikle Nesneye Yönelik Programlamaya yeni başlayanların kafasındaki büyük soru işaretlerinden bazıları "Interface ile Abstract arasındaki farklar nelerdir?, Ne zaman Interface ne zaman Abstract sınıfları kullanmalıyız ? …." gibi sorulardır.

Acı ve komik bir anımdan bahsetmeden olmaz. Okulda Java hocama neden Interface kullanırız diye sorduğumda bana "Genelde pek kullanılmaz. Fakat benim daha önce çalıştığım bilmem ne işyerinde bi abi interface kullanarak yazılım geliştirirdi. Biz 2 haftada yapıyorsak o bir haftada bitirirdi" demişti. Tabi daha sonra interface,abstract gibi kavramların nesneye yönelik programlamada ne kadar önemli kavramalar olduğunu öğrenince neden o abinin 1 haftada bitirdiğini anladım. :)

Interface ve Abstract kavramları nesneye yönelik programlamanın en temel ve önemli kavramlarından biridir.Önceki yazılarımızda Interface nedir ne zaman kullanılır?, ve Abstract nedir ne zaman kullanılır? ile bu kavramların nerede nasıl kullanıldığına ayrı ayrı konular altında değinmiştik. Eğer okumadıysanız öncelikle eski yazıları okumanızı tavsiye ederim. Aslında iki yazıyıda dikkatlice okuduğunuzda hangi durumda hangisi kullanılabilir diye karar verebilirsiniz.  Bu yazıda da genel bir özet yapıp bu kavramlar hakkında son düşüncelerimi ve kişisel tercihimi yazmak istedim.

Öncelikle daha önceki yazılarda Abstract sınıfların genellikle IS-A(dır,dir) ilişkilerinde,kalıtım(inheritance) özelliğini kullanarak kod tekrarını azaltmak için kullanıldığını söylemiştik. Interface sınıfların ise daha çok CAN-DO(yapabilir..) tarzı ilişkilerde değişen kavramları uygulamadan soyutlamak için kullanıldığını söylemiştik. Birde bu iki kavramın avantaj ve dezavantajlarına bakalım.Sonuçta Abstract bir sınıfın bütün metodlarını abstract yaparak onu da aynı bir interface gibide kullanabiliriz.

Öncelikle bence en büyük fark Abstract sınıfların tekli kalıtım(inheritance ) kullanması Interface sınıfların ise çoklu kalıtıma(multiple inheritance) izin vermesidir.Bildiğiniz gibi bir sınıfı başka bir sınıftan,ya da abstract bir sınıftan türettiğiniz zaman başka bir sınıftan daha türetme imkanınız olmuyor. Fakat interface sınıflarda durum daha farklı. Bir sınıfı istediğiniz kadar interfaceden türetebilirsiniz. Bu bakımdan interface sınıflar abstract sınıflardan oldukça daha esnek diyebiliriz. Fakat interface sınıflara baktığımızda abstract sınıflardan daha yavaş çalıştığı için hız bakımından devavantajı var diyebiliriz.

Şimdi hız mı esneklik mi diye bana soracak olursanız tabiki tercihim esneklikten yana olacaktır. Bu soruyu 2-3 sene önce sorsaydınız önce hız derdim büyük ihtimalle :)

Kendi kişisel tercihim olarak eğer soyutlamak istediğim yapıda ilişki CAN-DO ise ve hiç ortak kod yoksa sadece interface kullanırım. Fakat ortak kod içeriyorsa öncelikle bir interface(dikkat edin yine interface koyarım) koyup ardından ortak kodu kullanan bir abstract sınıf koyup onu o interfaceden türetir ve diğer sınıfları interface sınıfını kullanır hale getiririm.Burada interface i koymasakta olur fakat koymamın sebebi esnekliği arttırması ve özellikle Test Driven Development açısından Mock objelerin oluşturulması için interface kullanan sınıfların test edilmesinin çok daha kolay olması.Mock objelere ayrı bir konu başlığı altında değiniriz fazla uzatmayalım.

Eğer soyutlamak istediğim yapı IS-A ilişkisi ise sadece Abstract sınıf kullanırım. Abstract sınıfların sevmediğim yanı kalıtım(inheritance) diyebilirim.Kalıtım ilişkisi herzaman bağımlılığı(coupling) fazla olan bir ilişkidir. Yani türettiğiniz sınıfa tamamen bağımlı oluyorsunuz. Türettiğiniz sınıftaki herhangi bir değişiklik alt sınıfları etkiliyor. Fakat interface sınıflarda bu tarz bir problem daha az interface içindeki bir metodu değiştirmedikçe hiç bir sınıf bundan etkilenmiyor.Geliştirdiğiniz sınıflar tamamen plug-in mantığı ile interface sınıflar sayesinde hiç problemsiz değiştirilebiliyor. Bu yüzden kalıtıma her zaman şüpheyle yaklaşıyorum. Zaten Object OOP nin insanlar tarafından uzun yıllar kullanıldıktan sonra da aynı fikre varılmış. Kalıtımın bu problemli yapısı görüldüğü için daha sonra kalıtım yerine birleşim(Composition over Inheritance) kullanmak çoğu durumda tercih edilen bir yöntem.OOP için oldukça önemli olan bu konuyuda başka bir yazı konusu olarak ileriye bırakıyorum.

Konu ile ilgili yazıyı yazdığım gün konu ile alakalı çok güzel bir webcast e denk geldim. sizde izleyebilirsiniz. Konu Interface vs Abstract sınıflar. Burada konuşmacı benden daha katı olarak tamamen interface kullanılması gerektiğini savunuyor.Kısmen katılsamda bu şekile katı bir interface kullanımı her zaman doğru olmayabilir. Ayrıca konu ile ilgili daha derin bilgi edinmek için okumaya fırsat bulamadığım Interface Oriented Design kitabını okuyabilirsiniz. Ayrıca Wikide de  kısa bir bilgi verilmiş(Interface Based Programming).

Bu konuda şuanda aklıma gelenler bu kadar.Dediğim gibi konu bir blog yazısına sığmayacak kadar detaylı. En azından biraz ışık tutabilmiştirim. Ne kadar örneği bol tutmaya çalışsamda kendiniz yazmadıkça projelerinizde kullanmadıkta biraz havada kalabilir. O yüzden eller klavyeye diyorum…

Interface nedir,ne zaman kullanılır?

 

Interface içinde sadece kendisinden türeyen sınıfların içini doldurmak zorunda olduğu içi boş metod tanımlarının yapıldığı bir yapıdır. Kısacası kendisini kullanacak sınıflar için bir yerine getirmeleri gereken metodları belirten bir kontrat gibidir. Java ve C# dillerinde aşağıdaki gibi kullanılır.


interface Rapor{
    public void hazirla(Fatura fatura);
}

Bu interface kendinden türeyen tüm sınıfların public void hazirla(Fatura fatura) adında bir metoda sahip olmalarını zorunlu kılar aksi taktirde derleyici hata verecektir. Aşağıdaki gibi bir sınıfın bu interface’i nasıl uyguladığını gösteren koda bakalım.


class PdfRapor implements Rapor{
    public void hazirla(Fatura fatura) {
        //Faturayı pdf formatında hazırlayan kodlar
    }
}

Gördüğünüz gibi interface’den türeyen sınıflar bu şekilde interface’in belirttiği kurallara uyup içindeki metodları kendileri yazmak zorundadır.Kısacası interface’ler sınıfların yapabildiği şeyleri belirten kontratlardır diyebiliriz. Aklımıza gelmişken interface’lerin diğer özelliklerini de yazalım.

  • Interface’ler de Abstract sınıflar gibi new ile oluşturulamazlar
  • İçi dolu metod bulunduramazlar
  • public static final değişkenler dışında herhangi bir değişken bulunduramazlar
  • Bir sınıf birden fazla interface’den türeyebilir

Şimdi asıl önemli soruya geldik. Az önceki durumda neden interface kullandık?Aslında bunun sebebi tamamen bizden geliştirmemiz istenen yazılımın özelliklerine bağlı. "Yukarıdaki gibi bir kod yazmamızın ne gereği var?, Direk PdfRapor sınıfını interface’den türetmeden yazamazmıydık?" gibi sorular aklınıza gelmiş olabilir. Evet yazabilirdik tabi, fakat yazmamamızın sebebini şöyle açıklayalım.Öncelikle daha önceden müşteri ile konuştuğumuzda bizden şöyle bir talepte bulunmuştu: "Geliştireceğiniz yazılımda ben faturalarımı Pdf,Word,Excel formatlarında hazırlayabilmek istiyorum. Ayrıca ileride belki yeni formatlarda da sizden fatura isteyebilirim" diye daha önceden bizle konuşmuştu.Durum böyle olduğu için daha önceden Dependency Inversion makalesinde yazdığım gibi kodumuzun değişikliklerden etkilenmemesi, içinde bolca if-else yapılarının olmaması ve daha esnek olması için değişen kısmı bir interface kullanarak kodumuzdan soyutladık.Kodun içinde sadece aşağıdaki gibi interface kullanıldığı için ileride eklenecek yeni rapor formatlarından da etkilenmeyecektir.


class FaturaRaporFormu {
        private List calisanlar;
        private Fatura seciliFatura;
        private Rapor rapor;

        public FaturaRaporFormu(Rapor rapor){
               this.rapor = rapor;
       }

        public void FaturayaAitRapor(){
                this.rapor.hazirla(seciliFatura);
        }
}

Ayrıca interface ya da abstract sınıfların en büyük güzelliklerinden biride Polymorphism diyebiliriz. Interface ya da Abstract sınıfları kodun herhangi bir yerinde bir metoda parametre ya da metodun dönüş değeri,bir değişken, bir dizi tanımlamasında… gibi yerlerde kullanabiliriz. Ve bu kullanılan yerlerde Abstract ya da Interface’den türeyen herhangi bir sınıfı bu metoda,değişkene parametre olarak gönderdiğimizde ya da değişkene atadığımızda kod aynen çalışmaya devam edecek Java,C# dilleri Interface ya da Abstract sınıftan türeyen bütün alt sınıflara aynı şekilde tavranacaktır.

Gördüğünüz gibi interface kullanmak kodun esnekleğini arttırıyor. Fakat geliştirdiğimiz yazılımda ileride değişmeyecek bir kavramı sırf esneklik olsun diye interface kullanarak soyutlamakda kodu daha anlaşılması zor ve karmaşık hale getirecektir.Unutmayın yazılımın ve kodun basit ve sadece müşterinin ihtiyaçlarını karşılayanı makbuldür.Basitlik yazılım geliştirirken daima aklımızın bir köşesinde bulunmalı. Bu yüzden eğer müşteri bizden tek tip rapor isteseydi bu sefer interface kullanmamıza gerek kalmazdı.

Kısaca özetlersek Interface geliştirdiğimiz yazılımda aynı kavramın birden farklı şekilde uygulandığında bu kavramı soyutlayarak kodun esnekliğini,okunulabilirliğini arttırmak ve değişimin etkisini en aza indirmek için kullanılan yapılardır.Genelde Interface’den türeyen sınıflar arasında CAN-DO(yapabilir) ilişkisi vardır.Yukarıda neden kullanmamız gerektiğini umarım anlatabilmişimdir.

Abstract nedir, ne zaman kullanılır?

Abstract sınıflar içerisinde normal yani içi dolu metodların,değişkenlerin ve interface’lerdeki gibi abstract (boş) metodların tanımlanabildiği yapılardır.Bu sınıflar new kelimesi ile oluşturulamazlar.

Hemen uygulamaya geçelim örnek olarak arabalarla alakalı geliştirdiğimiz bir uygulamamız olsun. Sistemimizde bulunan çeşitli marka arabalara ait bazı özellikleri ekranda göstersin. Arabaların ağırlık,renk,model gibi ortak özellikleri ve cant kalınlığı,devir sayısı gibi kendilerine has özellikleri mevcut. Bunu ifade eden aşağıdaki gibi kodumuzu yazalım.

class Araba{
    private int agirlik;
    private String renk;
    private String model;

    public int getAgirlik() {
        return agirlik;
    }
    public void setAgirlik(int agirlik) {
        this.agirlik = agirlik;
    }
    public String getRenk() {
        return renk;
    }
    public void setRenk(String renk) {
        this.renk = renk;
    }
    public String getModel() {
        return model;
    }
    public void setModel(String model) {
        this.model = model;
    }
}
class Mercedes extends Araba{
    private int cantKalinligi;

    public int getCantKalinligi() {
        return cantKalinligi;
    }
    public void setCantKalinligi(int cantKalinligi) {
        this.cantKalinligi = cantKalinligi;
    }
}
class Ford extends Araba{
    private int devirSayisi;

    public int getDevirSayisi() {
        return devirSayisi;
    }
    public void setDevirSayisi(int devirSayisi) {
        this.devirSayisi = devirSayisi;
    }
}
class KullaniciEkrani{
    public void goster(Araba[] arabalar){
        for (int i = 0; i < arabalar.length; i++) {
            Araba araba = arabalar[i];
            System.out.println("Agirlik : "+araba.getAgirlik());
            System.out.println("Model : "+araba.getModel());
            System.out.println("Renk : "+araba.getRenk());
        }
    }
}
class AnaProgram{
    public static void main(String[] args) {
        Araba ford =new Ford();
        ford.setAgirlik(1000);
        ford.setModel("Fiesta");
        ford.setRenk("Gri");
        Araba mercedes=new Mercedes();
        mercedes.setAgirlik(2000);
        mercedes.setModel("E200");
        mercedes.setRenk("Siyah");

        Araba arabalar[]=new Araba[]{mercedes,ford};
        KullaniciEkrani ekran =new KullaniciEkrani();
        ekran.goster(arabalar);
    }
}


Yukarıdaki kodda gördüğünüz gibi Polymorphism sayesinde KullaniciEkrani sınıfı arabaların markalarından habersiz hepsini gösterebiliyor. Araba sınıfından türeyen her sınıf KullaniciEkrani sınıfında gösterilebiliyor.Buraya kadar gayet güzel. Şimdi müşteri bizden bu ekranda araçların saatte kaç litre benzin yaktıklarını da göstermemizi istedi.Fakat burada şöyle bir problem var her marka arabanın kaç litre benzin yaktığı kendi ağırlığına göre farklı hesaplanıyor.Araba sınıfına saatte yaktığı litre diye değişken eklesek olmayacak çünkü Mercedes bunu hesaplamak için farklı katsayı ile çarpıyor Ford farklı sayı ile çarpıyor.İşte bu noktada yardımımıza Abstract kavramı yetişiyor ve kodumuzu şöyle değiştiriyoruz.

abstract class Araba{
    private int agirlik;
    private String renk;
    private String model;

    public int getAgirlik() {
        return agirlik;
    }
    public void setAgirlik(int agirlik) {
        this.agirlik = agirlik;
    }
    public String getRenk() {
        return renk;
    }
    public void setRenk(String renk) {
        this.renk = renk;
    }
    public String getModel() {
        return model;
    }
    public void setModel(String model) {
        this.model = model;
    }

    public abstract int saateYaktigiBenzinLitresi();
}
class Mercedes extends Araba{
    private int cantKalinligi;

    public int getCantKalinligi() {
        return cantKalinligi;
    }
    public void setCantKalinligi(int cantKalinligi) {
        this.cantKalinligi = cantKalinligi;
    }

    public int saateYaktigiBenzinLitresi() {
        return getAgirlik()*2;
    }
}
class Ford extends Araba{
    private int devirSayisi;

    public int getDevirSayisi() {
        return devirSayisi;
    }
    public void setDevirSayisi(int devirSayisi) {
        this.devirSayisi = devirSayisi;
    }

    public int saateYaktigiBenzinLitresi() {
        return getAgirlik()*1;
    }
}
class KullaniciEkrani{
    public void goster(Araba[] arabalar){
        for (int i = 0; i < arabalar.length; i++) {
            Araba araba = arabalar[i];
            System.out.println("Agirlik : "+araba.getAgirlik());
            System.out.println("Model : "+araba.getModel());
            System.out.println("Renk : "+araba.getRenk());
            System.out.println("Yaktigi Lt. Benzin : "+araba.saateYaktigiBenzinLitresi());
        }
    }
}
class AnaProgram{
    public static void main(String[] args) {
        Araba ford =new Ford();
        ford.setAgirlik(1000);
        ford.setModel("Fiesta");
        ford.setRenk("Gri");
        Araba mercedes=new Mercedes();
        mercedes.setAgirlik(2000);
        mercedes.setModel("E200");
        mercedes.setRenk("Siyah");

        Araba arabalar[]=new Araba[]{mercedes,ford};
        KullaniciEkrani ekran =new KullaniciEkrani();
        ekran.goster(arabalar);
    }
}

Gördüğünüz gibi yukarıdaki kodda Araba sınıfına public abstract int saateYaktigiBenzinLitresi(); adında bir soyut metod ekledik. Bir sınıfın abstract metod bulundurabilmesi için Abstract bir sınıf olması gerekir. İkinci olarak Araba sınıfını abstract olarak değiştirdik. Bunu yapmamızın sebebi Araba sınıfı tarafından bu hesaplama işleminin nasıl yapılacağının bilinmemesidir. Yani bu hesaplamayı kendi yapmayıp kendinden türeyen sınıfların yapmasını şart koşmuştur. Bu hesaplamayı gördüğünüz gibi her marka araba sınıfı kendi katsayılarına göre kendi içinde yapıyor. Yukarıda Mercedes ve Ford sınıflarında saateYaktigiBenzinLitresi() metodunun nasıl farklı şekillerde yazıldığını gördünüz.Bu sayede KullaniciEkrani sınıfında Araba sınıfındaki saateYaktigiBenzinLitresi() metodunu çağırdığında her marka araba için kendi içlerinde yazdıkları saateYaktigiBenzinLitresi() metodu çalışacaktır.

Dikkat ederseniz uygulamamızada new kelimesi ile Araba nesnelerini zaten oluşturmuyorduk. Bizim oluşturduğumuz nesneler new Merceses,Ford gibi nesnelerdi.O yüzden Araba sınıfını normal sınıf tanımlamak tasarım bakımından zaten anlamsız.Burada Araba sınıfı kodun diğer kısımlarından Mercedes ve Ford gibi belirgin sınıfları soyutlamak ve sınıflarındaki ortak özellikleri(agirlik,renk,model) kodu tekrar yazmamak için oluşturulmuş bir soyut sınıf.Kod olarak baktığımızda KullaniciEkrani sınıfı Mercedes ve Ford sınıflarından habersiz sadece Araba sınıfını biliyor.

Gördüğünüz gibi Abstract sınıflar daha çok nesneler arasındaki ortak özelliklerin veya metodların bir üst sınıfta toplanarak kod tekrarını önlemek ve kodu diğer sınıflardan soyutlayarak değişimin etkisini en alt düzeye indirmektir.

Aklınızda bulunsun herhangi bir sınıfı Abstract bir sınıftan türetmek istediğinizde genel olarak dikkat etmeniz gereken durum aralarında IS-A özelliği olmasıdır.Yani Mercedes bir Arabadır, Ford bir Arabadır diyebiliriz.

Kısaca özetleyecek olursak Abstract sınıflar aralarında IS-A(..dır,..dir) ilişkisi olan sınıflardaki ortak özelliklerin ve metodların soyut üst sınıfda toplanması ondan sonra uygulamada bu soyut sınıfın kullanılarak uygulamanın diğer sınıflarından habersiz halde çalışmasını sağlamaktır. Nesneye yönelik tasarımda uygulamayı iyi soyutlamalar üzerine kurmak oldukça önemlidir. Uygulamada soyutlama oluşturmak için kullanılan önemli kavramlardan olan Abstract sınıfları küçük bir örnekle inceledik. Yakında diğer soyutlama mekanizması olan Interface kavramına değineceğiz. Tekrar görüşmek üzere…