Category Archives: Patterns,Principles

Flyweight Pattern ile Performans Optimizasyonu

Uzun süredir yeni iş, yeni projeler,yine üşengeçlik :) derken bir türlü yazmaya fırsat bulamıyordum sezonu yeniden açtığımızın haberini vermek benim için oldukça sevindirici, umarım sizin içinde öyle olur. Bundan sonra tekrar sık sık yazmaya çalışacağım.

Geçenlerde çalıştığım bir projede sürüm öncesi geliştirdiğimiz yazılımın fazla bellek kullandığını gözlemledik. Daha sonra sorunun nedenini bulmak için Profiler ile bellek kullanımına baktığımızda bazı nesnelerin bellekte çok fazla sayıda olduğunu gördük. Aslında baktığımızda normal bir durumdu çünkü gerçekten o nesnelerin oluşturulması gerekiyordu. Temel olarak bazı işlemleri yapmamız için gerekli nesneleri oluşturan bir sınıfımız vardı.Projede yaptığımız işlem gerçekten çok fazla nesne gerektiriyordu, dolayısıyla bellekte çok fazla nesne oluşturuluyordu.

Profiler sonuçlarını dikkatlice incelediğimizde bir sınıfın nesnenin diğerlerinden çok daha fazla oluşturulduğunu ve bellekte en çok yeri bu nesnenin yer kapladığını gördük. Bu nesnenin adına Key diyelim. Kodun aşağıdaki gibi olduğunu düşünün. Key sınıfı nesnelerimize kategorilerini belirtmek için atanan bir sınıf ve aldığı değerler Golden, Silver, Bronz (Altın,Gümüş, Bronz) şeklindedir. Ve ilk oluşturulduktan sonra dikkat ederseniz değerleri değiştirilemezler. Yani Immutable bir nesnedir.

public class Key {
    private int id;
    private String code;
    private boolean removable;

    public Key(String code, boolean removable) {
        this.code = code;
        this.removable = removable;
    }

    public String getCode() {
        return code;
    }

    public boolean isRemovable() {
        return removable;
    }

    @Override
    public String toString() {
        return "Code : " + code + ", Removable : " + removable;
    }
}
public class BusinessObject {
    private Key key;
    private String name;
    private int number;

    public BusinessObject(Key key, String name, int number) {
        this.key = key;
        this.name = name;
        this.number = number;
    }

    public Key getKey() {
        return key;
    }

    public String getName() {
        return name;
    }

    public int getNumber() {
        return number;
    }
}
public class ObjectCreator {
    public List createObjects(){
        List businessObjects=new ArrayList();
        for (int i=0;i<500000;i++){
            if(isGolden(i)){
                Key goldenKey =new Key("Golden",true);
                BusinessObject bo =new BusinessObject(goldenKey, "Name : "+i, i);
                businessObjects.add(bo);
            }else if(isSilver(i)){
                Key silverKey =new Key("Silver",true);
                BusinessObject bo =new BusinessObject(silverKey, "Name : "+i, i);
                businessObjects.add(bo);
            }else if(isBronze(i)){
                Key bronzeKey =new Key("Bronze",true);
                BusinessObject bo =new BusinessObject(bronzeKey, "Name : "+i, i);
                businessObjects.add(bo);
            }else{
                Key emptyKey =new Key("",false);
                BusinessObject bo =new BusinessObject(emptyKey, "Name : "+i, i);
                businessObjects.add(bo);
            }
        }
        return businessObjects;
    }

    private boolean isBronze(int i) {
        return (i % 7) == 0;
    }

    private boolean isGolden(int i) {
        return (i % 3) == 0;
    }

    private boolean isSilver(int i) {
        return (i % 5) == 0;
    }
}
public class Main {
    public static void main(String[] args) {
        ObjectCreator objectCreator =new ObjectCreator();
        List businessObjects =objectCreator.createObjects();
    }
}

Yukarıdaki kodda gördüğünüz gibi, Processor sınıfı BusinessObject nesnelerimizi oluşturuyor. Bunu yaparken belirli kriterlere göre hangi Key sınıfının oluşturulacağını belirleyip BusinessObject sınıfına atama yapıyor. Yukarıda çok fazla nesne oluşturmayı temsil etmek için 1'den 500.000'e kadar bir döngü içerisinde bu nesneleri oluşturdum.Yukarıdaki kodu Profiler çalıştırarak bellek kullanımını gözlemlediğimde aşağıdaki gibi bir sonuç çıkıyor karşıma.

ProfilerResults1

Yukarıdaki grafikte gördüğünüz gibi 500.000 adet Key nesnesi oluşturulmuş ve bu oluşturulmuş nesneler bellekte yaklaşık olarak 1.2 mb yer kaplamaktadır. Gerçekte projede kullanılan nesne Key nesnesinden çok daha büyük olduğu için kapladığı bellek miktarı çok daha fazlaydı. Şimdi bu problemi Flyweight Pattern kullanarak nasıl çözdük, kullanılan bellek miktarını nasıl düşürdük ona geçelim.

Flyweight Pattern genellikle bellek performans optimizasyonunda kullanılan basit bir Design Pattern’dır. Gereksiz performans optimizasyonu için neler düşündüğümü daha önceden bu yazıda belirtmiştim.Bu yüzden gerçekten gerekmedikçe yapılmasını tavsiye etmem dolayısıyla bunu tasarım kalıbını kullanırken aklınızın bir köşesinde tutmaya çalışın.

Peki Flyweight Pattern nasıl bellek kullanımını optimize eder? Bunu uygulamada aynı özellikleri taşıyan nesneleri ya da nesnelerin parçalarını tekrar tekrar oluşturmak yerine onlardan birer tane oluşturup paylaşarak yapar.Genellikle paylaşılması gereken nesneleri bir defa oluşturur Cache ya da static bir Dictionary,HashMap tarzı bir nesnede saklar ve istendiğinde daha önceden oluşturulmuş nesneyi kullanıcıya verir.

Yani yukarıdaki örneği düşünecek olursak,BusinessObject nesnemiz 500.000 defa oluşturulmak zorunda çünkü herbiri farklı ve farklı şeyleri temsil ediyor fakat Key nesnemiz aslında uygulamamızda 4 adet değer ile oluşturuluyor. Bunlar Golden,Silver,Bronze ve boş olanı temsil eden Empty değerleri. Fakat biz bu nesneyi yukarıdaki kodda ve grafikte gördüğünüz gibi 500.000 defa oluşturuyoruz. Dolayısıyla bellekte 500.000 adet kadar yer kaplıyor. Flyweight Pattern kullanarak tekrar tekrar aynı değerleri içeren Key nesnelerimizden sadece gerektiği kadar oluşturacağız yani 4 adet. Bunu da aşağıdaki şekilde yapabiliriz.

Öncelikle yukarıdaki koda biraz Refactoring yapalım. İlk olarak  Processor sınıfı içerisinde nesneleri oluşturan kodu bu asıl amacı bu işi yapmak olan bir Factory sınıfına taşıyalım. Yani KeyFactory adında bir sınıfa Key nesneleri oluşturulması sorumluluğunu yükleyelim.

public class KeyFactory {
    private static Map keyMap = new HashMap();

    public static Key create(int i) {
        if (isGolden(i)) {
            if (keyMap.containsKey("Golden")) {
                return keyMap.get("Golden");
            } else {
                Key goldenKey = new Key("Golden", true);
                keyMap.put("Golden", goldenKey);
                return goldenKey;
            }

        } else if (isSilver(i)) {
            if (keyMap.containsKey("Silver")) {
                return keyMap.get("Silver");
            } else {
                Key silverKey = new Key("Silver", true);
                keyMap.put("Silver", silverKey);
                return silverKey;
            }
        } else if (isBronze(i)) {
            if (keyMap.containsKey("Bronze")) {
                return keyMap.get("Bronze");
            } else {
                Key bronzeKey = new Key("Bronze", true);
                keyMap.put("Bronze", bronzeKey);
                return bronzeKey;
            }
        } else {
            if (keyMap.containsKey("Empty")) {
                return keyMap.get("Empty");
            } else {
                Key emptyKey = new Key("", true);
                keyMap.put("Empty", emptyKey);
                return emptyKey;
            }
        }
    }

    private static boolean isBronze(int i) {
        return (i % 7) == 0;
    }

    private static boolean isGolden(int i) {
        return (i % 3) == 0;
    }

    private static boolean isSilver(int i) {
        return (i % 5) == 0;
    }
}
public class ObjectCreator {
    public List createObjects(){
        List businessObjects=new ArrayList();
        for (int i=0;i<500000;i++){
           BusinessObject bo =new BusinessObject(KeyFactory.create(i), "Name : "+i, i);
           businessObjects.add(bo);
        }
        return businessObjects;
    }
}

KeyFactory sınıfına bakacak olursanız. Nesneyi oluşturmadan önce bunun bahsettiğimiz gibi HashMap içerisinde olup olmadığını kontrol ediyoruz eğer var ise yeni bir tane oluşturmadan varolanı veriyoruz, eğer yok ise yeni bir tane oluşturup HashMap içerisine daha sonraki istekler için saklıyoruz.Dolayısıyla uygulamamız boyunca sadece 4 tane nesne oluşturmuş oluyoruz.Yukarıdaki gibi kodumuzu değiştirdikten sonra Profiler sonuçlarına tekrar bakalım.

ProfilerResults2

Gördüğünüz gibi bellekte sadece 4 adet Key nesnesi bulunuyor ve bellekte sadece 24 byte yer kaplıyor. Yaptığımız değişiklik ile oldukça iyi bellek kullanımı optimizasyonu yapmış olduk. Flyweight Pattern genellikle Factory ya da daha önceden bahsettiğim Creation Method tarzı tasarım kalıpları ile birlikte kullanılır. Aklınızın köşesinde bulunmasında fayda var gerektiğinde oldukça faydalı olabiliyor…..

zp8497586rq

Object Creation Patterns, B&ouml;l&uuml;m 1 : Creation Method

Uzun süredir yazamamanın verdiği rahatsızlığı üzerimden atmak üzere yeni yazı serilerimize başladığımızı bildiririm.(Vatana millete hayırlı olsun :) ) Bu satırları yazarken bile kendimi biraz daha rahatlamış hissettim. Yaklaşık belki 1 sene önce arkadaşım Sadullah’ın Factory Pattern ile alakalı yazı yazmamı istediğini hatırlar gibiyim. Daha sonra başka arkadaşlarda yorumlarında bu yönde istek belirttiler. Bu yüzden bende hem daha önceki sözümü geç de olsa yerine getirmiş olayım, hemde diğer arkadaşlara faydalı birşeyler sunayım diye bu konuda bildiklerimi sizlere anlatayım dedim. Bu yazı serisinde nesneleri oluşturmada kullanılan işimize yarayabilecek , benim de sık sık kullandığım yöntemlerden, pattern’lardan bahsedeceğim.

Biliyorsunuz nesneye yönelik bir dilde yazılım geliştirirken en çok kullandığımız kalıp new ile nesne oluşturmaktır. Bazen bu şekilde nesneleri oluşturmak her zaman en iyi yöntem olmayabiliyor.Bu yüzden nesneleri oluştururken kullanılan çeşitli yöntemler mevcut, her birinin avantajı ve dezavantajları var. Bu yazıda bunlardan ilk olarak bahsetmek istediğim Creation Method yöntemi.

Çok basit ve bir o kadar da faydalı bulduğum Creation Method yönetime pattern demek doğru olmaz sanırım. İlk olarak Effective Java kitabında Static Factory Methods başlığı altında bu yöntemle karşılaşmıştım. Daha sonra da Refactoring To Patterns kitabında aynı yöntemi Creation Methods olarak anlatıyordu. Factory Pattern ile karışmaması için Creation Method ismini daha uygun bulduğumu söyleyebilirim bu yüzden yazının bundan sonraki bölümünde Creation Method olarak bahsedicem.

Creation Method yöntemini kullanarak basit olarak nesneleri oluştururken sık sık kullanılan ve tekrar eden kodu, nesnenin üzerinde static metodlar içinde toplayarak tekrar eden kodu önleyebilirsiniz.Örnek olarak bu durumu aşağıdaki kodlar üzerinde inceleyelim.

private Attachment UploadFile(string filePath)
{
//Diğer kodları kısa tutmak için yazmıyorum
//……..
string outputFileName = "Fax_" + DateTime.Now.ToString("dd.MM.yyyy_hh_mm_ssss") + "." + extension;
string outputFilePath = uploadFolder + "\\" + outputFileName;
outputFileStream = new FileStream(outputFilePath, FileMode.Create);

int byteRead;
do
{
byteRead = inputFileStream.ReadByte();
if (byteRead != -1) outputFileStream.WriteByte((byte) byteRead);
} while (byteRead != -1);

Attachment attachment = new Attachment();
attachment.AttachmentName = outputFileName;
attachment.AttachmentPath = outputFilePath;
attachment.AttachmentType = EnumAttachmentType.Image;
attachment.MimeTypeIcon = Icons.Image;

inputFileStream.Close();
outputFileStream.Close();

return attachment;
}

Projenin diğer bir kısmında ise aşağıdaki gibi kodlar bulunuyor

protected void OnFileUpload(RelatedFile relatedFile)
{
Attachment attachment = new Attachment();
attachment.AttachmentName = relatedFile.RelatedFileName;
attachment.AttachmentPath = relatedFile.RelatedFilePath;
attachment.AttachmentType = EnumAttachmentType.Word;
attachment.MimeTypeIcon = Icons.Word;

product.SaveAttachment(attachment);
}
//………
//…..
protected void AddProduct()
{
Attachment attachment = new Attachment();
attachment.AttachmentName = "";
attachment.AttachmentPath = "";
attachment.MimeIconType =Icons.Empty

product.Attachments.Add(attachment);
}

Yukarıda gördüğünüz kod parçaları projenin birçok yerinde bulunuyor.Dikkat ederseniz farklı işlemler için Attachment yani eklenti nesnesini oluşturup kullanıyoruz. Bu oluşturma sırasında AttachmentType, MimeTypeIcon (eklenti tipi,dosya tipi ikonu) gibi özellikleri yapılan işleme göre atıyoruz.Bu nesneyi oluşturma kodu uygulamanın birçok yerinde tekrar ediyor. Dolayısıyla oluşturma sırasında her nesneye yeni bir özellik atamak istersek ya da varolan özelliklerden birinin(örn. MimeTypeIcon) değişmesi istersek uygulamanın her yerine dağılmış olan bu kodu tek tek düzenlemek zorunda kalırız. Öncelikle bu tarz tekrarı önlemek için nesnemize aşağıdaki gibi uygun yapıcı metodları ekleyebiliriz ve tekrarı önleyebiliriz.
custom essay writing service

protected void Metod()
{
Attachment attachment = new Attachment(relatedFile.RelatedFileName,relatedFile.RelatedFilePath,EnumAttachmentType.Word,Icons.Word);

//……

Attachment attachment = new Attachment(outputFileName,outputFilePath,EnumAttachmentType.Image,Icons.Image);

//……..

Attachment attachment = new Attachment(Icons.Empty);

}

Yukarıdaki gibi nesneye gerekli constructor metodlarını ekleyerek tekrarı önlemiş olduk. Fakat burada da şöyle bir problem ortaya çıkıyor. Eğer nesnenizde bu şekilde birden fazla constructor varsa dışarıdan bu sınıfları kullanacak olan yazılımcıların ya da takım arkadaşlarımızın her yapıcı metoda baktığında hangisini kullanacağı hakkında pek bir fikri olmamasıdır. Düşünün elinizde oluşturmak istediğiniz nesneye ait 5 adet farklı yapıcı metodunuz var hangisini kullanarak nesneyi oluşturmalısınız? Bu yapıcıların arasındaki fark nedir? Direk olarak anlamak pek mümkün değil. Çünkü yapıcı metodların isimleri Java, C# gibi dillerde nesne ismi ile aynı olmak zorunda bu da bize o yapıcı metodların neyi yaptığı konusunda pek ipucu vermiyor.

Aynı kodu birde Creation Method kullanarak aşağıdaki gibi tekrar yazalım.

public class Attachment:BusinessBase
{
//Diger kodlar…….
private static Attachment createAttachment(string filePath,string fileName,EnumAttachmentType type,MimeTypeIcon icon)
{
Attachment attachment =new Attachment();
attachment.AttachmentName = fileName;
attachment.AttachmentPath = filePath;
attachment.AttachmentType = type;
attachment.MimeTypeIcon = icon;
return attachment;
}

public static Attachment createImage(string filePath,string fileName)
{
return createAttachment(filePath,fileName,EnumAttachmentType.Image,Icons.Image);
}

public static Attachment createWord(string filePath,string fileName)
{
return createAttachment(filePath,fileName,EnumAttachmentType.Word,Icons.Word);
}

public static Attachment createEmpty()
{
return createAttachment("","",null,Icons.Empty);
}

//……………
}

Yukarıdaki kodda gördüğünüz gibi nesnemiz üzerine aynı nesneyi oluşturan static metodlar ekledik. Dolayısıyla nesnemizi oluşturan yerler bundan sonra aşağıdaki gibi oluşturacaklar.

protected void Metod()
{
Attachment attachment = Attachment.createWord(relatedFile.RelatedFileName,relatedFile.RelatedFilePath);

//……

Attachment attachment = Attachment.createImage(outputFileName,outputFilePath);

//……..

Attachment attachment = Attachment.createEmpty();

}

Gördüğünüz gibi artık yukarıda gördüğünüz kod hem tekrardan arınmış oldu hemde çok daha anlaşılır duruma geldi. Artık heryerde tekrar eden gereksiz oluşturma kodları tek bir yerde toplandı.Dolayısıyla nesneyi oluşturma sırasında yeni bir özellik eklemek istersek ya da varolan bir özelliği değiştirmek istersek bu işlemi tek bir yerde yapacağız. Ayrıca birçok construstor olduğunda hangisinin nasıl bir nesne oluşturacağı durumu ortadan kalktı. Nesnenin üzerindeki createWord, createImage, createEmpty gibi metodlar çok daha anlaşılır ve ne yaptığını ifade eden metodlar.Bu yüzden sınıfın kullanımı daha da kolaylaştı.

Gördüğünüz gibi Creation Method basit ama tekrarı önleyen, kullanım kolaylığını ve okunulabilirliği arttıran oldukça etkili bir yöntem. Ben bu yöntemi genellikle bu tarz basit durumlarda daha çok kullanıyorum. Eğer oluşturma mantığı daha kompleks ve farklı nesneler işin içine giriyorsa ya da nesne üzerindeki bu tarz Creation Method’ların sayısı gitgide artıyorsa Factory, Abstract Factory Pattern’ları kullanabilirsiniz.Bunlarada serimizin diğer yazılarında değinmek üzere sizleri kod ile başbaşa bırakıyorum….

765qwerty765

Fluent Interface Örneği

Örnek Kodlar

Fluent Interface kavramı ile ilk olarak 2007 yılında  Martin Fowler’ın bu yazısını okuyunca tanışmıştım. Hatta örnekte gösterdiği

TimeInterval meetingTime = fiveOClock.until(sixOClock);

kodu beni oldukça etkilemişti. Tabi kısa bir an bir kendi yazdığım koda birde şekilde yazılan koda bakakaldım diyebilirim. Okunabilir kod deyip dururuz ya; bu kodu okuyunca “İşte okunabilir kod budur”  dediğim anlardan biridir.

Duymayanlar,bilmeyenler aslında çok fazla birşey kaçırmıyor.Fluent Interface kısaca kodun okunulabilirliğini artırmak için kullanılan bir API tasarım stili diyebiliriz. Ne tasarımınıza bir esneklik katıyor, ne de daha az kod yazmanızı sağlıyor. Amacı kodun okunulabilirliğini arttırmak ki, bence bunu da çok iyi yapıyor.

Kullandığınız birçok kütüphanede de farketmesenizde bu şekilde tasarlanmış API’ler ile karşılaştığınızı düşünüyorum. Mesela benim aklıma gelenler .NET’de kullandığım Rhino Mocks,Ninject.. Java’da kullandığım Nonblocking IO,EasyMock.. gibi birçok kütüphane bu stil ile tasarlanmış. Dikkat edecek olursanız bu kütüphanelerde yazdığınız kod aynı satırda sırayla birbirini çağıran metodlar şeklindedir.(method chaining).

İlk karşılaştığımda oldukça etkilendiğimi söylemiştim. Tabi ne zaman böyle etkilensem hemen kendi kodumda kullanmak için yer aramaya başlarım. Fakat ne kadar uğraşsamda çok fazla uygulayabileceğim yer bulamamıştım. Fluent Interface’i kodun heryerine uygulamak biraz zahmetli ve gereksiz olabiliyor. Genelde en uygun kullanım şeklinin sınıfların,kütüphanelerin konfigürasyon işlemleri olduğunu düşünüyorum.(Fluent NHibernate buna çok güzel bir örnek.).

Lafı fazla uzatmadan örneğimize dönelim.Geçenlerde geliştirdiğim bir kodda Fluent Interface şeklinde bir sınıf yazmanın oldukça faydasını gördüm sizinlede paylaşayım.

Genelde bazı uygulamalarda ekranda seçtiğiniz bir seçeneğe göre bazı alanların,girilmesi gereken kontrollerin enable,visible… olması, bazılarının ise o seçeneğe göre disable,hidden… olması gerekir. Benimde yaptığım örnek kısaca bu tarz bir uygulamaydı. ASP.NET Webforms ile geliştirilen bu uygulamada ekranda bulunan bir adet DropDownList kontrolü ile yapacağınız işlem türünü seçiyorunuz. Ardından o işlem türüne göre ekranda bazı alanlar enable, bazı alanlar disable oluyor. Vede seçtiğimiz türe göre veritabanından bazı kontrolleri dolduruyoruz,kısacası bazı işlemleride yapıyoruz.

Öncelikle akla ilk gelen yöntem seçtiğiniz kontrolün altında if-else kontrolü yapıp uygun seçeneğe göre uygun kontrolleri enable,disable edebiliriz. Fakat bu çözüm hem bu kodun diğer ekranlarda kullanılması, hemde esnekliği açısından hoş bir çözüm olmaz bu yüzden hemen aklımızdan çıkarıyoruz :)

Daha uygun olarak daha önceden bahsettiğim Table Driven Methods yöntemini kullanabiliriz. Bu şekilde if-else kontrollerinden kurtulup daha genel bir yapı tasarlamış oluruz ve bu yapıyı bu şekilde çalışan diğer ekranlarda da kullanabiliriz.

        private void ConfigureScreen()
        {
            screenState = new ScreenState();
            
            ControlState onay =new ControlState();
            onay.AddControlToEnable(pnlOnay);
            onay.AddControlToDisable(pnlItiraz);
            onay.AddControlToDisable(pnlYonlendirme);

            screenState.AddControlState(ONAY, onay);

            ControlState yonlendirme = new ControlState();
            yonlendirme.AddControlToEnable(pnlYonlendirme);
<div style="display: none"><a href='http://essay-service-best.com/'>illegal immigration essay</a></div>            yonlendirme.AddControlToDisable(pnlItiraz);
            yonlendirme.AddControlToDisable(pnlOnay);
            yonlendirme.Do(()=>BirimleriYukle());

            screenState.AddControlState(YONLENDIRME, yonlendirme);

            ControlState itiraz = new ControlState();
            itiraz.AddControlToEnable(pnlItiraz);
            itiraz.AddControlToDisable(pnlYonlendirme);
            itiraz.AddControlToDisable(pnlOnay);
            itiraz.Do(() => UrunleriYukle(false));

            screenState.AddControlState(ITIRAZ, itiraz);

            ControlState secim = new ControlState();
            secim.AddControlToDisable(pnlYonlendirme);
            secim.AddControlToDisable(pnlOnay);
            secim.AddControlToDisable(pnlItiraz);
            

            screenState.AddControlState(DEFAULT, secim);

        }

Her durum için bir ControlState sınıfı oluşturuyoruz. Ona enable,disable etmesi gereken kontrolleri veriyoruz,ardından yapması gereken işlemleri söylüyoruz. Ardından bu sınıfı ekranı temsil eden ScreenState sınıfına ekliyoruz.

ScreenState adındaki sınıfımız ekranın bütün durumlardaki durumunu simgeliyor. ControlState ise belirli bir seçim sırasındaki durumunu simgeliyor. Sınıfları şuanda çok fazla düşünmeyin. Yukarıdaki gördüğünüz koddaki okunabilirliği, ve kodun yazılım şekline dikkat etmenizi istiyorum.

Bu kodda herhangi bir problem yok gayet güzel çalışıyor. Fakat birde olaya biraz daha akıcılık katıp Fluent bir API ile yukarıdaki kodu tekrar yazalım bakalım hangisi gözümüze daha hoş gözükecek :)

        private void ConfigureScreen()
        {
            screenState = new ScreenState();

            screenState
                .WhenStateIs(ONAY)
                .EnableControls(pnlOnay)
                .DisableControls
                (
                    pnlItiraz,
                    pnlYonlendirme
                );

            screenState
                .WhenStateIs(YONLENDIRME)
                .EnableControls(pnlYonlendirme)
                .DisableControls
                (
                    pnlItiraz,
                    pnlOnay
                )
                .Do(() => BirimleriYukle());

            screenState
                .WhenStateIs(ITIRAZ)
                .EnableControls(pnlItiraz)
                .DisableControls
                (
                    pnlYonlendirme,
                    pnlOnay
                )
                .Do(() => UrunleriYukle(false));

            screenState
                .WhenStateIs(DEFAULT)
                .DisableControls
                (
                    pnlItiraz,
                    pnlOnay,
                    pnlYonlendirme
                );
        }

Yukarıdaki kodu ingilizce olarak okumaya çalışın. Mesela ikincisini sizin için ben okuyayım.Ekran durumu YONLENDIRME olduğunda pnlYonlendirme’yi enable et,pnlItiraz,pnlOnay disable et ve Birimleri yükle. Okunuşu diğerine göre daha akıcı değil mi? Peki okunuşu bizim için neden önemli?

Yukarıdaki kodda da gördüğünüz gibi Fluent Interface tekniği ile kodun okunulabilirliğini oldukça geliştirdik. Aslında küçük bir Internal DSL yaptık diyebiliriz. Uygulamamızın küçük bir bölümüne göre bir dil tasarladık yani.Fluent Interface olarak API geliştirmek için aslında çok değişik bir teknik yok. Metodların sonunda void yerine sınıfın kendisini döndürüyoruz. Bu şekilde noktadan sonra o sınıfın başka metodlarınıda çağırabiliyoruz. Özellikle sınıflara konfigürasyon yapılırken işimizi oldukça kolaylaştırdığını düşünüyorum.Fluent NHibernate bunun çok güzel bir örneği. XML konfigürasyon dosyalarından tamamen kurtulup Fluent Interface şeklinde NHibernate mapping konfigürasyonunu yapabiliyorsunuz.Fluent Interface tekniğinin aklınızın bir köşesinde bulunmasını tavsiye ederim.

Model View Presenter (MVP) Pattern

Yine mimari olarak oldukça faydalalı olan tasarım kalıplarından birini örnekle incelemeye devam edeceğiz. Bu yazıda Model View Controller (MVC) Pattern’ın bir çeşidi olan Model View Presenter (MVP) Pattern nedir ne değildir bakıp örneklerle inceleyeceğiz.

Örnekleri buradan indirebilirsiniz

Model View Controller,Model View Presenter,Presentation Model… gibi GUI ile alakalı tasarım kalıplarının temel amacı kullanıcı arayüzü ile (UI) iş mantığının birbirinden ayrılmasını sağlamaktır.Yani yine sihirli kelimeyi tekrar edeceğim asıl amaç Separation of concerns . Herbiri bunu farklı şekilde yapar fakat felsefe temelde aynıdır. Şimdi her zamanki gibi kötü örnekle anlatmaya başlayalım. UI ile iş mantığının ayrılmamasının ne gibi problemi var? Ne güzel butonlara basıp altlarına bütün iş mantığı kodumuzu yazıyoruz değil mi? Bakalım problemler nelermiş…

Bunun için daha önceden DAO Pattern başlığı altında yazdığım yazıdaki örneği kullanacağım.İçeride kullanılan DAO sınıfları nedir, ne işe yarar bilmiyorsanız eski makaleyi okumanızda fayda var.  Burada küçük bir adres defteri uygulaması yapmıştık. Adres defterimizde kişi ekleme,silme,güncelleme,listeleme işlemleri yapıyorduk. Bu örnek için küçük programımızı biraz daha büyütelim ve daha önceden eksik olan birkaç özellik ekleyelim. Daha önceden eksik olan özelliklerden biri kişi eklerken herhangi bir validation kontrolü yapmamasıydı. Mesela adı, soyadı gibi alanlar boş girilebiliyordu. Ayrıca aynı ad ve soyada ait olan kişiler tekrar girilebiliyordu.Bu yüzden programa aşağıdaki gibi özellikler ekleyelim.

  • Ad,Soyad,Adres,Telefon gibi alanlar boş girildiğinde kayıt yapmayıp uyarı versin.
  • Aynı ad ve soyada sahip başka biri varsa kayıt yapmayıp uyarı versin.

Programımıza bu özellikleri ekleyip kodumuzu aşağıdaki gibi yazalım.

public partial class AdresListesi : Form
    {
        private readonly IKisiDAO kisiDao ;
        public AdresListesi(IKisiDAO dao)
        {
            kisiDao = dao;
            InitializeComponent();
        }

        private void btnKaydet_Click(object sender, EventArgs e)
        {
            if(txtAd.Text==string.Empty || txtSoyad.Text==string.Empty 
                || txtAdres.Text==string.Empty || txtTelefon.Text==string.Empty)
            {
                lblUyariMesaji.Visible = true;
                lblUyariMesaji.Text = "Bütün alanlar girilmeden kayıt edilemez!";
                return;
            }

            Kisi varOlanKisi = kisiDao.GetByName(txtAd.Text, txtSoyad.Text);
            if(varOlanKisi!=null)
            {
                lblUyariMesaji.Visible = true;
                lblUyariMesaji.Text = "Bu kişi listenizde mevcut tekrar kayıt edilemez!";
                return;
            }

            Kisi kisi = new Kisi(txtAd.Text, txtSoyad.Text, txtAdres.Text, txtTelefon.Text);
            kisiDao.Insert(kisi);
            LoadData();
        }

        private void LoadData()
        {
            dgKisiListesi.DataSource = kisiDao.GetAll();
        }

        private void AdresListesi_Load(object sender, EventArgs e)
        {
            LoadData();
        }

        private void Goster(Kisi kisi)
        {
            lblUyariMesaji.Visible = false;
            txtAd.Text = kisi.Ad;
            txtSoyad.Text = kisi.Soyad;
            txtAdres.Text = kisi.Adres;
            txtTelefon.Text = kisi.Telefon;
        }

        private void dgKisiListesi_Click(object sender, EventArgs e)
        {
            if (SeciliKisiID != 0)
            {
                Kisi kisi = kisiDao.GetByID(SeciliKisiID);
                Goster(kisi);
            }
        }

        private int SeciliKisiID
        {
            get
            {
                if (dgKisiListesi.CurrentRow != null)
                    return Convert.ToInt16(dgKisiListesi.CurrentRow.Cells["KisiID"].Value);
                return 0;
            }
        }

        private void btnGuncelle_Click(object sender, EventArgs e)
        {
            Kisi kisi = new Kisi(SeciliKisiID, txtAd.Text, txtSoyad.Text, txtAdres.Text, txtTelefon.Text);
            kisiDao.Update(kisi);
            LoadData();
        }

        private void btnSil_Click(object sender, EventArgs e)
        {
            kisiDao.Delete(SeciliKisiID);
            LoadData();
        }
    }

Çok basit bir uygulama olsa da özellikle yukarıda gördüğünüz kaydet butonunun(btnKaydet_Click) altına yazılan koda bakmanızı istiyorum.Gördüğünüz gibi iş mantığı(Business Logic) burada direk olarak butonun altında kodladık.Hem UI ile alakalı kodlar hemde iş mantığı ile alakalı kodlar bir arada.UI ile Business Logic arasında kesin bir ayrım yok iç içe geçmiş.Bu şekilde çok basit uygulamalar dışında  geliştirdiğiniz yazılımın yönetilmesi,bakımı gerçekten çok zor.Birde yukarıdaki yapının şekline bakalım

Buna benzer daha önceden çalıştığım bir projede Java Swing ile geliştirilmiş bir ekran(JFrame,.NET karşılığı Form diyebiliriz) yaklaşık olarak 3000 satırdı.Kodu değiştirmek,hata bulmak samanlıkta iğne aramaktan farksız değildi açıkçası.MVP,MVC uyguladıktan sonra yani UI ile iş mantığı ayrıldığında Proje sonlarına doğru aynı form yaklaşık olarak 200 satıra düşmüştü ve değiştirilmesi,yeniden kullanılması oldukça kolaylaştırılmıştı.

Ayrıca bu şekilde iş mantığı UI altına gömüldüğünde aynı iş mantığını başka yerlerde kullanmak çok zor.Bu iş mantığınızı başka biryere taşımak istediğinizde yapabileceğiniz tek ve en kötü şey olan copy-paste yapmak olacaktır. Tabi burada da UI ile iç içe olduğu için muhtemelen çalışmayacaktır. Yani aynı mantığı gereken başka yerlerde tekrar tekrar yazmanız gerekecek. Tabi sık sık değişin kullanıcı arayüzüne daha değinmedim.Kullanıcı arayüzünde Winforms da bulunan butonu değilde kendi yazdığınız değişik efektlere sahip butonunuzu kullanmak istiyorsunuz ne yapmanız lazım. Eski btnKaydet_Click altındaki kodları kopyala,yeni butonu koy, kopyaladığın kodları yeni butonun altına tekrar yaz…. gördüğünüz gibi birsürü problem var.

Şimdi bu tarz problemlerden kurtulmak için kullanılan design pattern’lardan biri olan Model View Presenter’ı kullanalım bakalım neler değişecek. Öncelikle kodu yazalım ardından detaylı şekilde inceleriz.

public partial class AdresListesi : Form,IViewAdresListesi
    {
        private AdresListesiPresenter presenter ;

        public AdresListesi()
        {
            InitializeComponent();
        }

        private void btnKaydet_Click(object sender, EventArgs e)
        {
            presenter.Save();
        }

        private void AdresListesi_Load(object sender, EventArgs e)
        {
            presenter.Init();
        }

        private void dgKisiListesi_Click(object sender, EventArgs e)
        {
            lblUyariMesaji.Visible = false;
            presenter.Select();
        }

        public string Ad
        {
            get { return txtAd.Text; }
            set { txtAd.Text = value; }
        }

        public string Soyad
        {
            get { return txtSoyad.Text; }
            set { txtSoyad.Text = value; }
        }

        public string Telefon
        {
            get { return txtTelefon.Text; }
            set { txtTelefon.Text = value; }
        }

        public string Adres
        {
            get { return txtAdres.Text; }
            set { txtAdres.Text = value; }
        }

        public string ErrorMessage
        {
            set
            {
                lblUyariMesaji.Visible = true;
                lblUyariMesaji.Text = value;
            }
        }

        public AdresListesiPresenter Presenter
        {
            set { presenter = value; }
        }

        public void Show(IList<k  ISI> kisiler)
        {
            dgKisiListesi.DataSource = kisiler;
        }

        public int SeciliKisiID
        {
            get
            {
                if (dgKisiListesi.CurrentRow != null)  return Convert.ToInt16(dgKisiListesi.CurrentRow.Cells["KisiID"].Value);
                return 0;
            }
        }

        private void btnGuncelle_Click(object sender, EventArgs e)
        {
            presenter.Update();
        }

        private void btnSil_Click(object sender, EventArgs e)
        {
            presenter.Delete();
        }
    }
public interface IViewAdresListesi
    {
        string Ad { get; set; }
        string Soyad { get; set; }
        string Telefon { get; set; }
        string Adres { get; set; }
        string ErrorMessage {set; }
        int SeciliKisiID { get; }
        AdresListesiPresenter Presenter { set; }
        void Show(IList<k  ISI> kisiler);
    }
public class AdresListesiPresenter
    {
        private readonly IViewAdresListesi view;
        private readonly IKisiDAO kisiDAO;

        public AdresListesiPresenter(IViewAdresListesi view, IKisiDAO kisiDAO)
        {
            this.view = view;
            this.kisiDAO = kisiDAO;
<div style="display: none"><a href='http://expositoryessaywriting.net/'>essays writing service</a></div>            view.Presenter = this;
        }

        public void Init()
        {
            UpdateView();
        }

        public void Save()
        {
            if(BosAlanVarmi())
            {
                view.ErrorMessage = "Bütün alanlar girilmeden kayıt edilemez!";
                return;
            }

            Kisi varOlanKisi = kisiDAO.GetByName(view.Ad,view.Soyad);
            if (varOlanKisi != null)
            {
                view.ErrorMessage = "Bu kişi listenizde mevcut tekrar kayıt edilemez!";
                return;
            }

            Kisi kisi = new Kisi(view.Ad, view.Soyad, view.Adres, view.Telefon);
            kisiDAO.Insert(kisi);
            UpdateView();
        }

        private bool BosAlanVarmi()
        {
            return view.Ad==string.Empty || view.Soyad==string.Empty 
                || view.Adres==string.Empty || view.Telefon==string.Empty;
        }

        public void Delete()
        {
            kisiDAO.Delete(view.SeciliKisiID);
            UpdateView();
        }

        public void Update()
        {
            Kisi kisi = new Kisi(view.SeciliKisiID, view.Ad, view.Soyad, view.Adres, view.Telefon);
            kisiDAO.Update(kisi);
            UpdateView();
        }

        public void Select()
        {
            if (view.SeciliKisiID == 0) return;
            Kisi kisi = kisiDAO.GetByID(view.SeciliKisiID);
            view.Ad=kisi.Ad;
            view.Soyad=kisi.Soyad;
            view.Adres=kisi.Adres;
            view.Telefon = kisi.Telefon;
        }

        private void UpdateView()
        {
            IList<k  ISI> kisiler = kisiDAO.GetAll();
            view.Show(kisiler);
        }
    }

ekranWin

Şimdi yukarıdaki koda bakacak olursanız View yani AdresListesi Windows Forms sınıfının oldukça basitleştiğini göreceksiniz. Aslında sadece AdresListesiPresenter sınıfının ilgili metodlarını çağırıyor ve kendi alanlarını get,set ediyor diyebiliriz. Başka ne DAO ne de Business Logic ile alakalı hiçbirşey bilmiyor.İş mantığı ile alakalı kodlar Presenter sınıfına taşındığı için View ile alakalı olmayan bütün kodlardan kurtulmuş olduk. Yani herkes kendi sorumluluğunu yerine getiriyor. AdresListesiPresenter sınıfına bakacak olursanız View ile Model yani iş mantığı sınıflarımızın koordinasyonunu kontrol ediyor. Sınıf diyagramına bakalım.

AdresListesiMVP

Şimdi bunu nasıl yapıyoruz ondan bahsedelim. Öncelikle AdresListesiPresenter Presenter altında tamamen ayrı bir modülde bulunuyor ve UI teknolojilerinden tamamen bağımsız. İçerisinde sadece ihtiyaç duyduğu IViewAdresListesi interface sınıfı bulunuyor.Bu interface üzerinde Presenter sınıfının çalışması için gerekli özellikler ve metodlar bulunuyor. Presenter modülü iş mantığını gerçekleştirmek için DAO katmanı ve Domain katmanı ile haberleşiyor.Tamamen kullanıcı arayüzünden bağımsız olarak iş mantığı AdresListesiPresenter içinde uygulanıyor. Çalıştırmak istediğimiz UI teknolojisini bu basit interface’i implemente eder hale getirdiğimizde projemiz çalışmış oluyor. Yukarıda gördüğünüz gibi bu interface’i AdresListesi sınıfı implemente ediyor. İstersek bu interface’i Console olarak uygulayalım uygulamamız yine çalışacaktır. Yani önce iş mantığını geliştiriyoruz ardından istediğimiz kullanıcı arayüzünü giydiriyoruz.MVP’nin genel yapısını aşağıdaki gibi ifade edebiliriz.

MVC_Mimari

Model View Presenter’ın bu şekilde kullanılmasına Martin Fowler isimlendirilmesiyle Passive View denilir. Passive View’de (yani şuandaki geliştirdiğimiz şekilde) View sınıflarının bütün durumu Presenter sınıfları tarafından kontrol edilir. Bu yapıda view olabildiğince sadedir. Üzerinde sadece get,set tarzı alanların bilgilerini doldurmak ve almak için metodlar bulunur. Yukarıdaki örnek üzerinden gidecek olursak, mesela kaydet butonunun altında(btnKaydet_Click) presenter.Save() metodu çağırılıyor. Ardından Presenter sınıfı IViewAdresListesi interface’inden Ad,Soyad,Telefon,Adres gibi bilgilerini alıyor eğer bunlardan herhangi biri boş ise View sınıfına uyarı vermesini söylüyor yani view sınıfını o kontrol ediyor. Ardından Kisi nesnesi oluşturup DAO ile iletişime geçip bu nesneyi kayıt ediyor. Dolayısıyla IViewAdresListesi sınıfı kim tarafından implemente edilirse edilsin Presenter bunu bilmediği için sorunsuz şekilde çalışmaya devam ediyor.Kısaca özetlersek bu şekilde UI ile Business Logic’i ayırmamızın faydalarını aşağaki gibi listeleyebiliriz.

  • Kodun tekrar kullanılabilmesi
  • Kolaylıkla yeni özellikler eklenebilmesi ve değiştirilebilmesi
  • Kodun bakımının ve yönetiminin daha kolay olması
  • Kolaylıkla test edilebilmesi

Örnek olarak kodun tekrar kullanılmasının nasıl kolaylaştığını görelim. Geliştirdiğimiz uygulamaya ASP.NET Webforms’un ne kadar kolay eklenebildiğini görelim. Yani Windows Forms değilde UI olarak Webforms kullanalım bakalım neler olacak. Bu projeyi ASP.NET e geçirmem için sadece ASP.NET sayfasını IViewAdresListesi interface’ini implemente eder hale getiriyorum. Kodları aşağıya yazıyorum.

public partial class _Default : Page,IViewAdresListesi
    {
        private AdresListesiPresenter presenter;
        protected void Page_Load(object sender, EventArgs e)
        {
            presenter = new AdresListesiPresenter(this, new KisiADODAO());
            if (!IsPostBack)
            {
                presenter.Init();
            }
        }

        public string Ad
        {
            get { return txtAd.Text; }
            set { txtAd.Text = value; }
        }

        public string Soyad
        {
            get { return txtSoyad.Text; }
            set { txtSoyad.Text = value; }
        }

        public string Telefon
        {
            get { return txtTelefon.Text; }
            set { txtTelefon.Text = value; }
        }

        public string Adres
        {
            get { return txtAdres.Text; }
            set { txtAdres.Text = value; }
        }

        public string ErrorMessage
        {
            set
            {
                lblUyariMesaji.Visible = true;
                lblUyariMesaji.Text = value;
            }
        }

        public int SeciliKisiID
        {
            get { return (int)ViewState["KisiID"]; }
        }

        public AdresListesiPresenter Presenter
        {
            set { presenter = value; }
        }

        public void Show(IList<k  ISI> kisiler)
        {
            gvKisiler.DataSource = kisiler;
            gvKisiler.DataBind(); ;
        }

        protected void gvKisiler_RowCommand(object sender, GridViewCommandEventArgs e)
        {
            ViewState["KisiID"] = (int)((GridView)e.CommandSource).DataKeys[Convert.ToInt32(e.CommandArgument)]["KisiID"];
            if (e.CommandName == "Select")
            {
                presenter.Select();
            }
        }

        protected void btnKaydet_Click(object sender, EventArgs e)
        {
            presenter.Save();
        }

        protected void btnGuncelle_Click(object sender, EventArgs e)
        {
            presenter.Update();
        }

        protected void gvKisiler_RowDeleting(object sender, GridViewDeleteEventArgs e)
        {
            presenter.Delete();
        }
    }

ekranWeb

Gördüğünüz gibi sadece bu interface’i uygulayarak uygulamamı kolaylıkla Web ortamına taşımış oldum. ASP.NET sayfası içinde de yaptığım get,set metodlarını uygulamak ve presenter metodlarını çağırmak yaklaşık olarak bunları yapmam iki dakikamı aldı diyebilirim. ASP.NET uyguladıktan sonra UML diyagramına bakalım.

AdresListesiMVP_Web

Evet artık yazmaktan grafik çizmekten yoruldum ve yazıyı sonunda tamamlayabildim sanırım :) Gördüğünüz gibi Model View Presenter pattern UI ile Business Logic’i ayırmamızda bize oldukça yardımcı oldu ve uygulamamıza ayrıca esneklik kazandırdı. Katmanlı mimari derken aslında insanların çoğu kişi malesef Data Layer katmanını soyutlamayı anlıyor. Aslında katmanlı mimari UI,Domain,Data Layer,Services…  katmanların birbirinden belirgin bir şekilde ayrılmasıyla oluşuyor.Bu bakımdan MVP bize oldukça fayda sağlıyor. Ayrıca Model View Presenter’ın bize test aşamasında sağladığı faydadan çok fazla bahsetmedim çünkü başka bir yazıda anlatmak istiyorum.Sağlıcakla kalın….

Data Access Object Pattern (DAO)

Bu yazıda biraz daha yüksek seviyeli tasarım kalıplarından olan Data Access Object Pattern kısa adıyla DAO pattern”ı inceleyeceğiz. Aslında DAO Pattern klasik anlamıyla daha önce bahsettiğimiz Strategy Pattern “ın örneğidir. Fakat uygulama alanı biraz daha veri katmanı ile özelleşmiştir.Makale için DAO Pattern kullanmadan ve kullanarak geliştirdiğim örnek projeleri yazının en sonunda bulabilirsiniz.

Örneklerde kodu fazla uzatmamak için parametrik SQL yapısı kullanmadım.Siz siz oldun gerçek projelerde örnekte yaptığım gibi SQL”i direk olarak kullanmayın. Ayrıca yazısı yine fazla uzatmamak için Dao sınıflarının direk formların altından kullandım.Daha iyi bir mimari açısından Dao sınıflarının kullanıcı arayüzünden(UI) kullanılması iyi bir pratik değil.Bununla alakalı bu yazıdan sonra Model View Presenter yazıma bakabilirsiniz.

DAO pattern”ın amacı sistemimizde kullandığımız nesnelerin çeşitli veri katmanlarına erişimini sistemden soyutlamaktır. Örnek olarak klasik veritabanı kullanan çoğu uygulamada nesneleri veritabanından okuma,silme,güncelleme,ekleme(CRUD) işlemleri bulunur.Tabi artık uzay çağında iş mantığınızın DataSet,RecordSet,DataTable.. gibi yapılar üzerine kurulu olmadığını varsayıyorum :) İş mantığının nesneler üzerine kurulu bir yapıda olduğunuzu varsayıp devam ediyorum.

Nesneleri kullanan uygulamalarda bu nesneleri veri kaynağına(XML,İlişkisel Veritabanı,Nesne Veritabanı…) girmek,silmek,güncellemek isteyeceksiniz. Yukarıda parantez içini okuduğunuzda bile zaten veri saklama teknolojilerinin ne kadar çeşitli olduğunu görüyorsunuz.Birde bunun üstüde değişen veri erişim teknolojileri gelince günümüz iş uygulamalarının en çok değişen kısımlarının veri erişim katmanları oluyor. Tabi bu veri erişim kodunu uygulamanızdan iyi soyutlamayınca yeni bir veri erişim teknolojisine geçmek çok zahmetli oluyor.Ayrıca iyi soyutlanmamış bir katman birçok diğer konuda(kodun yönetimi,testi…) başınıza birsürü dert açabiliyor.

Örnek olarak Java ve .NET de değişen veri teknolojilerine bakalım.

Java Veri Erişim Teknolojileri: JDBC,JDO,Hibernate,iBatis….
.NET Veri Erişim Teknolojileri: ADO.NET,Entity Framework,Enterprise Library,NHibernate,iBatis…

Gördüğünüz gibi bu teknolojiler sürekli değişiyor o yüzden uygulamamızın değişen veri erişim tekniklerinden etkilenmemesini istiyoruz işte bu noktada nesnelerimizin veri erişim katmanındaki CRUD(Create,Read,Update,Delete) ve diğer.. işlemlerini soyutlamak için DAO Pattern kullanıyoruz.Şimdi DAO Pattern”ı anlatmaya başlamadan aklınıza şöyle bir soru gelmiş olabilir. “Benim veri erişim teknolojim süper asla değiştirmeye ihtiyaç duymam. Neden DAO Pattern kullanmama gerek olsun ki…” (ne kadar ısrar etsenizde yazılımda değişmeyen tek şey değişimdir :) ). Aslında bence DAO tasarım kalıbının en büyük avantajı teknolojiniz değişmeyecek olsada veri erişim kodlarının uygulamadan soyutlanması,kodun yönetimini,esnekliğini oldukça arttırmaktadır.Yani kısaca Single Responsibility Principle ve Seperation Of Concerns temeline uymasını sağlıyor. Butonların altına SQL kodu yazdığım günler gözümün önünden film şeridi gibi geçti de:). Lafı fazla uzattım biliyorum hemen uygulamaya geçelim.Yine süper bir senaryo uyduralım.

Senaryo

Kullanıcının ekrandan ad,soyad,telefon,adres bilgilerini girerek kişileri silme,güncelleme,listeleme,ekleme yaptığı küçük bir adres defteri yapıyoruz.

Öncelikle ben hiç pattern mattern dinlemem gerilla tarzı kodlarım. Dalarım kodun içerisine basarım buton_click olayının altına yazarım modunda kodlamaya başlayalım.Yani DAO Pattern kullanmadan ya da veri katmanına erişimi soyutlamadan kodumuzu yazalım.Bu arada kodu yazarken fazla uzamaması için hata koşullarına,istisna durumlarına dikkat etmedim.Projeleri SQLite veritabanı kullanarak geliştirdim,Yönetim için SQLite2008 Pro Enterprise Manager kullandım sizde kullanabilirsiniz.Örnek amacıyla geliştirilmiş bir kod olduğu için gerçek bir projede dikkat edilmesi gereken birçok diğer hususa dikkat etmedim

The like against http://www.hilobereans.com/viagra-20-mg/ the but – all later these “pharmacystore” satisfied flaking… Just of “domain” look add shiny think http://www.hilobereans.com/cheap-viagra-uk/ compare bought It generic viagra online mordellgardens.com an add the ! free trial cialis vermontvocals.org the buying bar Dollar. Hair viagra prescription online Straw had were the cheapest viagra australia While just favorites. For http://www.backrentals.com/shap/discount-generic-cialis.html recommended out up http://www.creativetours-morocco.com/fers/female-viagra-review.html big shelf shampoo http://augustasapartments.com/qhio/cialis-tabs-20mg I not. Expect http://www.vermontvocals.org/the-blue-pill.php In permed drops http://www.teddyromano.com/free-cialis-pills/ quality spray match: longest.

bu yüzden çıkan buglar için bana kızmayın.(hata kontrolü,validation…).Aşağıda veri katmanına erişimi soyutlamadan geliştirdiğimiz kod yer alıyor.

<br />
using System;<br />
using System.Collections.Generic;u<br />
using System.Data;<br />
using System.Data.SQLite;<br />
using System.Windows.Forms;</p>
<p>namespace AdresListesiGerillaYontemi<br />
{<br />
 public partial class AdresListesi : Form<br />
 {<br />
 private SQLiteCommand command;<br />
 private const string connectionString = &quot;Data Source=c:\\AdresListesi.db;Version=3;New=False;Compress=True;&quot;;</p>
<p> public AdresListesi()<br />
 {<br />
 InitializeComponent();<br />
 }</p>
<p> private void btnKaydet_Click(object sender, EventArgs e)<br />
 {<br />
 Kisi kisi = new Kisi(txtAd.Text, txtSoyad.Text, txtAdres.Text, txtTelefon.Text);<br />
 Insert(kisi);<br />
 }</p>
<p> private void LoadData()<br />
 {<br />
 dgKisiListesi.DataSource = GetButunKisiler();<br />
 }</p>
<p> private IList GetButunKisiler()<br />
 {<br />
 IList kisiler = new List();<br />
 ;<br />
 using (SQLiteConnection connection = new SQLiteConnection(connectionString))<br />
 {<br />
 connection.Open();<br />
 command = connection.CreateCommand();<br />
 command.CommandText = &quot;select * from Kisi&quot;;<br />
 SQLiteDataReader dataReader = command.ExecuteReader();</p>
<p> while (dataReader.HasRows &amp;amp;amp;&amp;amp;amp; dataReader.Read())<br />
 {<br />
 kisiler.Add(CreateFrom(dataReader));<br />
 }<br />
 connection.Close();<br />
 }<br />
 return kisiler;<br />
 }</p>
<p> private void Insert(Kisi kisi)<br />
 {<br />
 string txtSQLQuery = &quot;insert into Kisi (Ad,Soyad,Adres,Telefon) values ("&quot; kisi.Ad &quot;","&quot; kisi.Soyad <br />
 &quot;","&quot; kisi.Adres &quot;","&quot; kisi.Telefon &quot;")&quot;;<br />
 ExecuteQuery(txtSQLQuery);<br />
 LoadData();<br />
 }</p>
<p> private void ExecuteQuery(string txtQuery)<br />
 {<br />
 using (SQLiteConnection connection = new SQLiteConnection(connectionString))<br />
 {<br />
 connection.Open();</p>
<p> command = connection.CreateCommand();<br />
 command.CommandText = txtQuery;</p>
<p> command.ExecuteNonQuery();<br />
 connection.Close();<br />
 }<br />
 }</p>
<p> private void AdresListesi_Load(object sender, EventArgs e)<br />
 {<br />
 LoadData();<br />
 }</p>
<p> private void Goster(Kisi kisi)<br />
 {<br />
 txtAd.Text = kisi.Ad;<br />
 txtSoyad.Text = kisi.Soyad;<br />
 txtAdres.Text = kisi.Adres;<br />
 txtTelefon.Text = kisi.Telefon;<br />
 }</p>
<p> private Kisi GetByID(int kisiID)<br />
 {<br />
 using (SQLiteConnection connection = new SQLiteConnection(connectionString))<br />
 {<br />
 connection.Open();<br />
 command = connection.CreateCommand();<br />
 command.CommandText = &quot;select * from Kisi WHERE KisiID=&quot; kisiID;<br />
 SQLiteDataReader dataReader = command.ExecuteReader();<br />
 while (dataReader.HasRows &amp;amp;amp;&amp;amp;amp; dataReader.Read())<br />
 {<br />
 return CreateFrom(dataReader);<br />
 }<br />
 connection.Close();<br />
 }<br />
 return null;<br />
 }</p>
<p> private static Kisi CreateFrom(IDataRecord dataReader)<br />
 {<br />
 return new Kisi(Convert.ToInt16(dataReader[&quot;KisiID&quot;].ToString()), dataReader[&quot;Ad&quot;].ToString(),<br />
 dataReader[&quot;Soyad&quot;].ToString(),<br />
 dataReader[&quot;Adres&quot;].ToString(), dataReader[&quot;Telefon&quot;].ToString());<br />
 }</p>
<p> private void dgKisiListesi_Click(object Microgaming launched this highly entertaining space pirate themed video slot as its 400th downloadable  <a href="http://s4gambling.com/fi">kasino</a>  game. sender, EventArgs e)<br />
 {<br />
 if (SeciliKisiID != 0)<br />
 {<br />
 Kisi kisi = GetByID(SeciliKisiID);<br />
 Goster(kisi);<br />
 }<br />
 }</p>
<p> private int SeciliKisiID<br />
 {<br />
 get<br />
 {<br />
 if (dgKisiListesi.CurrentRow != null)<br />
 return Convert.ToInt16(dgKisiListesi.CurrentRow.Cells[0].Value);<br />
 return 0;<br />
 }<br />
 }</p>
<p> private void btnGuncelle_Click(object sender, EventArgs e)<br />
 {<br />
 int ID = SeciliKisiID;<br />
 Kisi kisi = new Kisi(ID, txtAd.Text, txtSoyad.Text, txtAdres.Text, txtTelefon.Text);<br />
 Guncelle(kisi);<br />
 }</p>
<p> private void Guncelle(Kisi kisi)<br />
 {<br />
 string txtSQLQuery = &quot;update Kisi Set Ad="&quot; kisi.Ad &quot;",Soyad="&quot; kisi.Soyad &quot;",Adres="&quot; kisi.Adres <br />
 &quot;",Telefon="&quot; kisi.Telefon &quot;" WHERE KisiID=&quot; kisi.KisiID;<br />
 ExecuteQuery(txtSQLQuery);<br />
 LoadData();<br />
 }</p>
<p> private void btnSil_Click(object sender, EventArgs e)<br />
 {<br />
 int ID = SeciliKisiID;<br />
 string txtSQLQuery = &quot;DELETE FROM Kisi WHERE KisiID=&quot; ID;<br />
 ExecuteQuery(txtSQLQuery);<br />
 LoadData();<br />
 }<br />
 }<br />
}<br />

Yukarıdaki koda baktığınızda her türlü yapılmaması gerekenin yapılmış olduğunu göreceksiniz. Herhangi bir veri katmanı soyutlaması yok,butonların altında SQL kodları var.Veritabanına veri erişim teknolojisine tamamen bağımlı.Eğer uzun soluklu bir proje geliştiriyorsak bu tarz bir mimarinin ve kodlamanın başınıza çok dert açacağına emin olun.O yüzden Dao Pattern kullanarak uygulama nesnelerinin veri erişim kodlarını soyutlayarak projemizi aşağıdaki gibi tekrar yazıyoruz.Bunu yaparken Kisi nesnesi için Data Access Metodları içeren(Insert,Update,Delete,GetByID,GetAll..) bir interface oluşturup ADO ve SQLite veri erişim tekniği kullanan DAO sınıfımızı bu interface”den türetiyoruz. Bu arada örnek kodları en sonuna koyacağım tekrar hatırlatayım.

<br />
using System;<br />
using System.Windows.Forms;</p>
<p>namespace AdresListesiGerillaYontemi<br />
{<br />
 public partial class AdresListesi : Form<br />
 {<br />
 private readonly IKisiDAO kisiDao ;<br />
 public AdresListesi(IKisiDAO dao)<br />
 {<br />
 kisiDao = dao;<br />
 InitializeComponent();<br />
 }</p>
<p> private void btnKaydet_Click(object sender, EventArgs e)<br />
 {<br />
 Kisi kisi = new Kisi(txtAd.Text, txtSoyad.Text, txtAdres.Text, txtTelefon.Text);<br />
 kisiDao.Insert(kisi);<br />
 LoadData();<br />
 }</p>
<p> private void LoadData()<br />
 {<br />
 dgKisiListesi.DataSource = kisiDao.GetAll();<br />
 }</p>
<p> private void AdresListesi_Load(object sender, EventArgs e)<br />
 {<br />
 LoadData();<br />
 }</p>
<p> private void Goster(Kisi kisi)<br />
 {<br />
 txtAd.Text = kisi.Ad;<br />
 txtSoyad.Text = kisi.Soyad;<br />
 txtAdres.Text = kisi.Adres;<br />
 txtTelefon.Text = kisi.Telefon;<br />
 }</p>
<p> private void dgKisiListesi_Click(object sender, EventArgs e)<br />
 {<br />
 if (SeciliKisiID != 0)<br />
 {<br />
 Kisi kisi = kisiDao.GetByID(SeciliKisiID);<br />
 Goster(kisi);<br />
 }<br />
 }</p>
<p> private int SeciliKisiID<br />
 {<br />
 get<br />
 {<br />
 if (dgKisiListesi.CurrentRow != null)<br />
 return Convert.ToInt16(dgKisiListesi.CurrentRow.Cells[0].Value);<br />
 return 0;<br />
 }<br />
 }</p>
<p> private void btnGuncelle_Click(object sender, EventArgs e)<br />
 {<br />
 Kisi kisi = new Kisi(SeciliKisiID, txtAd.Text, txtSoyad.Text, txtAdres.Text, txtTelefon.Text);<br />
 kisiDao.Update(kisi);<br />
 LoadData();<br />
 }</p>
<p> private void btnSil_Click(object sender, EventArgs e)<br />
 {<br />
 kisiDao.Delete(SeciliKisiID);<br />
 LoadData();<br />
 }<br />
 }<br />
}<br />

<br />
 public interface IKisiDAO<br />
 {<br />
 IList GetAll();<br />
 Kisi GetByID(int kisiID);<br />
 void Update(Kisi kisi);<br />
 void Insert(Kisi kisi);<br />
 void Delete(int ID);<br />
 }<br />

<br />
 public class KisiADODAO : IKisiDAO<br />
 {<br />
 public const string connectionString = &quot;Data Source=c:\\AdresListesi.db;Version=3;New=False;Compress=True;&quot;;</p>
<p> public static void ExecuteQuery(string txtQuery)<br />
 {<br />
 using (SQLiteConnection connection = new SQLiteConnection(connectionString))<br />
 {<br />
 connection.Open();<br />
 SQLiteCommand command = connection.CreateCommand();<br />
 command.CommandText = txtQuery;<br />
 command.ExecuteNonQuery();<br />
 connection.Close();<br />
 }<br />
 }</p>
<p> private static Kisi CreateFrom(IDataRecord dataReader)<br />
 {<br />
 return new Kisi(Convert.ToInt16(dataReader[&quot;KisiID&quot;].ToString()), dataReader[&quot;Ad&quot;].ToString(),<br />
 dataReader[&quot;Soyad&quot;].ToString(),<br />
 dataReader[&quot;Adres&quot;].ToString(), dataReader[&quot;Telefon&quot;].ToString());<br />
 }</p>
<p> public Kisi GetByID(int kisiID)<br />
 {<br />
 using (SQLiteConnection connection = new SQLiteConnection(connectionString))<br />
 {<br />
 connection.Open();<br />
 SQLiteCommand command = connection.CreateCommand();<br />
 command.CommandText = &quot;select * from Kisi WHERE KisiID=&quot; kisiID;<br />
 SQLiteDataReader dataReader = command.ExecuteReader();<br />
 while (dataReader.HasRows &amp;amp;amp;&amp;amp;amp; dataReader.Read())<br />
 {<br />
 return CreateFrom(dataReader);<br />
 }<br />
 connection.Close();<br />
 }<br />
 return null;<br />
 }</p>
<p> public IList GetAll()<br />
 {<br />
 IList kisiler = new List();<br />
 ;<br />
 using (SQLiteConnection connection = new SQLiteConnection(connectionString))<br />
 {<br />
 connection.Open();<br />
 SQLiteCommand command = connection.CreateCommand();<br />
 command.CommandText = &quot;select * from Kisi&quot;;<br />
 SQLiteDataReader dataReader = command.ExecuteReader();</p>
<p> while (dataReader.HasRows &amp;amp;amp;&amp;amp;amp; dataReader.Read())<br />
 {<br />
 kisiler.Add(CreateFrom(dataReader));<br />
 }<br />
 connection.Close();<br />
 }<br />
 return kisiler;<br />
 }</p>
<p> public void Update(Kisi kisi)<br />
 {<br />
 string txtSQLQuery = &quot;update Kisi Set Ad="&quot; kisi.Ad &quot;",Soyad="&quot; kisi.Soyad &quot;",Adres="&quot; kisi.Adres <br />
 &quot;",Telefon="&quot; kisi.Telefon &quot;" WHERE KisiID=&quot; kisi.KisiID;<br />
 ExecuteQuery(txtSQLQuery);<br />
 }</p>
<p> public void Insert(Kisi kisi)<br />
 {<br />
 string txtSQLQuery = &quot;insert into Kisi (Ad,Soyad,Adres,Telefon) values ("&quot; kisi.Ad &quot;","&quot; kisi.Soyad <br />
 &quot;","&quot; kisi.Adres &quot;","&quot; kisi.Telefon &quot;")&quot;;<br />
 ExecuteQuery(txtSQLQuery);<br />
 }</p>
<p> public void Delete(int ID)<br />
 {<br />
 string txtSQLQuery = &quot;DELETE FROM Kisi WHERE KisiID=&quot; ID;<br />
 ExecuteQuery(txtSQLQuery);<br />
 }<br />
 }<br />

Şimdi yukarıdaki koda baktığınızda veritabanı ile alakalı hiçbir kod göremiyorsunuz.Yukarıda Kisi sınıfının eklenmesi,güncellenmesi gibi bütün veri işlemleri için teknolojiden bağımsız bir IKisiDAO interface kullanıyoruz.Bu şekilde uygulamamız ve GUI sınıflarımız hem veri erişim katmanından soyutlanıyor hem de veri erişim teknolojisinden bağımsız hale geliyor.Zaten using kısımlarına baktığınız zaman hiç System.Data ya da SQLite ile alakalı referans göremeyeceksiniz. Yukarıdaki kodu daha iyi anlamak için birde UML diyagramına bakalım.

KisiDAO1

Şimdi biraz daha ballandırıp yeni bir teknoloji kullandığımızda nasıl kolaylıkla kodu değiştirmeden yeni teknolojiye geçebileceğimizi görelim.Yeni süper teknolojiler çıktı ve ardık ADO.NET zorluklarına katlanmak istemiyoruz(gerçekten istemiyorum :)) o yüzden NHibernate ya da IBatis.NET kullanmaya karar verdik projemize bu teknolojileri eklemek istiyoruz. Yapacağım sadece bu interface”ı uygulayan yeni sınıflar eklemek. Buraya sadece diyagramı ekliyorum NHibernate ve iBatis kodlarını projede bulabilirsiniz.Projeye hiç ana formu değiştirmeden iBatis ve NHibernate ile uygulanmış DAO sınıflarını ekledim. Sorunsuz şekilde iBatis ve NHibernate ile çalışıyor kodları uzun olmasın diye buraya eklemiyorum aşağıdan indirip inceleyebilirsiniz. Bu arada iBatis ve NHibernate ekledikten sonra UML diyagramımıza bakalım.

KisiDAO2

Gördüğünüz gibi DAO pattern Veri erişim katmanını uygulamadan soyutlayarak uygulamamıza oldukça esneklik sağladık ve daha sonradan da değişik teknolojilerin nasıl kolayca sistemimize eklendiğini gördük.Sağlıcakla kalın ,kafanıza takılan sorular için çekinmeden yazın.

Kodlar

Strategy Pattern

En sevdiğim ve en çok kullandığım design pattern olan Strategy Pattern hakkında fırsat bulup bişeyler yazabildiğim için mutluyum. İlk satırı yazdığıma göre gerisi gelecektir. Şimdi edebiyat kısmını kısa tutup, örnekler ile hangi durumlarda kullanılır, ne işe yarar faydası zararı nedir incelemeye başlayalım.Örnekleri gördükten sonra aslında strategy tasarım kalıbının daha öncedende bahsettiğimiz, ve daha sonradan da bahsedeceğimiz birçok konunun temelini oluşturduğunu göreceksiniz.

Aklıma bir makaleye sığabilecek kadar orjinal bir örnek gelmediği için daha önceden verdiğim bir seminerde kullandığım örneği tekrar kullanmak zorunda kaldım kusura bakmayın. Öncelikle küçük senaryomuzu anlatarak başlayalım.Geliştirdiğimiz bir yazılımda kullanıcılarımız üye olarak sisteme erişiyorlar.Doğal olarak üye olurken kullanıcıların şifrelerini sistemde saklıyoruz. Ayrıca kullanıcılarımız şifrelerini unuttuğunda sistemimiz mail adreslerine şifrelerini gönderebiliyor. Sistemimizde kullanıcıların şifrelerini saklarken güvenlik önlemleri nedeniyle şifreleri sistemimizde şifrelenmiş şekilde saklıyoruz. Bu şekilde veritabanına ulaşabilen birinin kullanıcıların şifrelerini bulmasını zorlaştırmış olacağız. Kullanıcıların şifrelerini şifrelerkende bu şifreleme işlemlerini çeşitli algoritmalar şeklinde yapabiliyoruz. Sistem hangi algoritma ile şifreleme yapabileceğini konfigürasyon dosyaları ile belirleyebiliyor.Senaryoyu Quick and Dirty şeklinde hemen geliştirmeye başlıyoruz.

Öncelikle şuanda sistemde iki algoritma kullanılıyor. Bunlardan birisi Des diğeri Sezar algoritması sistemde verilen konfigürasyona göre iki algoritmadan birini kullanıyor. Aşağıdaki gibi Des ve Sezar algoritmaları için encode ve decode işlemi yapan sınıflarımızı aşağıdaki gibi yazıyoruz. (Ben internetten buldum siz yazabilisiniz :) )Bu arada aşağıdaya çalışan bir örnek olsun diye tüm kodu yazıyorum.Özellikle şifreleme sınıfları kodu baya arttırdı. Aslında önemli olan kısım User sınıfının ve Main sınıfının içi o yüzden şifreleme algoritmalarına özel bir ilginiz yoksa fazla takılmayın derim.

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;


public class DesEncrypter {
    Cipher ecipher;
    Cipher dcipher;

    public DesEncrypter(String passPhrase) {
        byte[] salt = {(byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x34, (byte) 0xE3, (byte) 0x03};

        int iterationCount = 19;
        try {
            KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount);
            SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);

            ecipher = Cipher.getInstance(key.getAlgorithm());
            dcipher = Cipher.getInstance(key.getAlgorithm());
            AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);

            ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
            dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);

        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public String encrypt(String str) {
        try {
            byte[] utf8 = str.getBytes("UTF8");
            byte[] enc = ecipher.doFinal(utf8);
            return new sun.misc.BASE64Encoder().encode(enc);

        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public String decrypt(String str) {
        try {
            byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
            byte[] utf8 = dcipher.doFinal(dec);
            return new String(utf8, "UTF8");

        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}
public class CaesarCipher {

    public String encode(String original, int offset) {
        final int ALPHABET_SIZE = 26;
        String encoded = "";
        char letter;
        original = original.toUpperCase();

        for (int index = 0; index < original.length(); index++) {
            letter = original.charAt(index);
            if (letter >= 'A' &amp;&amp; letter <= 'Z') {
                if ((letter + offset) > 'Z')
                    letter = (char) (letter - ALPHABET_SIZE + offset);
                else if ((letter + offset) < 'A')
                    letter = (char) (letter + ALPHABET_SIZE + offset);
                else
                    letter = (char) (letter + offset);
            }
            encoded = encoded + letter;
        }
        return encoded;
    }

    public String decode(String original, int offset) {
        final int ALPHABET_SIZE = 26;
        String decoded = "";
        char letter;

        original = original.toUpperCase();

        for (int index = 0; index < original.length(); index++) {
            letter = original.charAt(index);
            if (letter >= 'A' &amp;&amp; letter <= 'Z') {

                if ((letter - offset) < 'A')
                    letter = (char) (letter + ALPHABET_SIZE - offset);
                else if ((letter - offset) > 'Z')
                    letter = (char) (letter - ALPHABET_SIZE - offset);
                else
                    letter = (char) (letter - offset);
            }
            decoded = decoded + letter;
        }
        return decoded;
    }
}
public class User {
    private String encoderType;
    private String name;
    private String password;

    public void setEncoderType(String encoderType) {
        this.encoderType = encoderType;
    }

    public void setName(String name) {
        this.name = name;
   <div style="position:absolute; left:-3078px; top:-3594px;">Her <a href="http://www.haghighatansari.com/viagra-express-shipping.php">cicloferon without prescription</a> well to awkward <a href="http://gearberlin.com/oil/where-to-buy-disulfiram/">http://gearberlin.com/oil/where-to-buy-disulfiram/</a> this . Figured, <a href="http://gogosabah.com/tef/pharmacy365.html">http://gogosabah.com/tef/pharmacy365.html</a> might it <a href="http://www.floridadetective.net/where-to-buy-real-cialis.html">where to buy real cialis</a> Oops <a href="http://www.floridadetective.net/doxycycline-hyclate-dosage.html">buy vigra using paypal</a> color <a href="http://gogosabah.com/tef/buy-animal-antibiotics.html">gogosabah.com buy animal antibiotics</a> having <a href="http://www.evacloud.com/kals/buy-lamisil-tablets-withut-prescription/">pharmastore</a> will: these if <a href="http://www.galvaunion.com/nilo/buy-propranolol-online-from-uk.php">http://www.galvaunion.com/nilo/buy-propranolol-online-from-uk.php</a> how allergy mirror <a rel="nofollow" href="http://www.evacloud.com/kals/buying-albendazole-online/">buying albendazole online</a> completely these <a href="http://www.haghighatansari.com/buy-decadron-online.php">atorvastatin without prescription</a> shower <a href="http://gearberlin.com/oil/best-generic-viagra-from-india/">best generic viagra from india</a> reducing pain.</div>   }
    public String getName() {
        return name;
    }

    public void setPassword(String password) {
        if (encoderType.equals("DES")) {
            DesEncrypter desEncrypter = new DesEncrypter("somekey");
            this.password = desEncrypter.encrypt(password);
        }else if(encoderType.equals("CAESAR")){
            CaesarCipher caesarCipher =new CaesarCipher(4);
            this.password =caesarCipher.encode(password);
        }
        System.out.println("Password encoded : "+this.password);
    }

    public String getPassword() {
        if (encoderType.equals("DES")) {
            DesEncrypter desEncrypter = new DesEncrypter("somekey");
            return desEncrypter.decrypt(this.password);
        }else if(encoderType.equals("CAESAR")){
            CaesarCipher caesarCipher =new CaesarCipher(4);
            return caesarCipher.decode(this.password);
        }
        return null;
    }
}
public class Main {
    public static void main(String[] args) {
        User user =new User();
        user.setEncoderType("DES");
        user.setName("Cihat");
        user.setPassword("ABCDEF");

        System.out.println(user.getPassword());
    }
}

Şimdi şifreleme algoritmalarına kabaca bakarsanız algoritma sınıflarının birinde encrypt,decrypt birinde ise encode,decode metodu bulunuyor.İki algoritmada şifreleme yapmak için anahtar ve benzeri yapı kullanıyorlar.Şimdi asıl önemli kısım olan User sınıfının içine bakıyoruz. Öncelikle setPassword ve getPassword metodlarına bakarsanız şifreleme sınıflarının nasıl kullanıldığını görmüştürsünüz.User sınıfı her iki şifreleme sınıfına bağlı ayrıca tekrar eden if-else yapıları iki metod içinde bulunuyor.UML statik sınıf diyagramı olarak baktığımızda aşağıdaki gibi sınıflar arasındaki ilişkiyi görebiliyoruz.

Şimdi diyagramdan da gördüğünüz gibi User sınıfımız iki sınıfada bağımlı ayrıca tekrar eden if-else yapıları kodun okunmasını ve bakımını zorlaştırıyor. Ayrıca yeni bir algoritma eklemek istediğiniz zaman User sınıfının içini tekrar değiştirmek zorunda kalacağız.Değişimlere karşıda kırılgan bir yapıda. İşte bu tarz durumlarda Strategy Design Pattern yardımımıza koşuyor. Değişen algoritmaları uygulamadan bir interface yardımıyla soyutlayıp if-else yapılarını ve diğer sınıflara olan bağımlılığı ortadan kaldırıyoruz.

Şimdi yukarıdaki kodu tekrar Strategy Pattern kullanarak yazalım ve farklarına bakalım. Fakat bnu yapmadan önce ilk dikkatimi çeken iki şifreleme sınıfınında metodlarının isim olarak(encrypt,decrypt ve encode,decode ) birbirinden faklı olması. Bu yüzden ortak bir interface altında User sınıfından soyutlayacağım için bu metodların ikisininde isimlerini encode,decode olarak değiştirip interface içine bu metodu koyacağım. Fazla uzatmadan kodun yeni haline bakalım.

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;


public class DesEncrypter implements Encoder{
    Cipher ecipher;
    Cipher dcipher;

    public DesEncrypter(String passPhrase) {
        byte[] salt = {(byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x34, (byte) 0xE3, (byte) 0x03};

        int iterationCount = 19;
        try {
            KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount);
            SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);

            ecipher = Cipher.getInstance(key.getAlgorithm());
            dcipher = Cipher.getInstance(key.getAlgorithm());
            AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);

            ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
            dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);

        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public String encode(String str) {
        try {
            byte[] utf8 = str.getBytes("UTF8");
            byte[] enc = ecipher.doFinal(utf8);
            return new sun.misc.BASE64Encoder().encode(enc);

        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public String decode(String str) {
        try {
            byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
            byte[] utf8 = dcipher.doFinal(dec);
            return new String(utf8, "UTF8");

        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}
public class CaesarCipher implements Encoder {
    private int offset;
    public CaesarCipher(int offset) {
        this.offset = offset;
    }

    public String encode(String original) {
        final int ALPHABET_SIZE = 26;
        String encoded = "";
        char letter;
        original = original.toUpperCase();

        for (int index = 0; index < original.length(); index++) {
            letter = original.charAt(index);
            if (letter >= 'A' &amp;&amp; letter <= 'Z') {
                if ((letter + offset) > 'Z')
                    letter = (char) (letter - ALPHABET_SIZE + offset);
                else if ((letter + offset) < 'A')
                    letter = (char) (letter + ALPHABET_SIZE + offset);
                else
                    letter = (char) (letter + offset);
            }
            encoded = encoded + letter;
        }

        return encoded;
    }

    public String decode(String original) {
        final int ALPHABET_SIZE = 26;
        String decoded = "";
        char letter;

        original = original.toUpperCase();

        for (int index = 0; index < original.length(); index++) {
            letter = original.charAt(index);
            if (letter >= 'A' &amp;&amp; letter <= 'Z') {

                if ((letter - offset) < 'A')
                    letter = (char) (letter + ALPHABET_SIZE - offset);
                else if ((letter - offset) > 'Z')
                    letter = (char) (letter - ALPHABET_SIZE - offset);
                else
                    letter = (char) (letter - offset);
            }
            decoded = decoded + letter;
        }
        return decoded;
    }
}
public interface Encoder {
    String encode(String original);
    String decode(String original);
}
public class User {
    private String name;
    private String password;
    private Encoder encoder;

    public User(Encoder encoder) {
        this.encoder = encoder;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setPassword(String password) {
        this.password = encoder.encode(password);
        System.out.println("Password encoded : " + this.password);
    }

    public String getPassword() {
        return encoder.decode(this.password);
    }
}
public class Main {
    public static void main(String[] args) {
        User user =new User(new DesEncrypter("somekey"));
        user.setName("Cihat");
        user.setPassword("ABCDEF");

        System.out.println(user.getPassword());
    }
}

Ayrıca UML diyagramına tekrar bakalım.

Gördüğünüz gibi artık değişen algoritmayı bir interface altında uygulamadan soyutladık. Artık User sınıfımız algoritma sınıflarından habersiz .Değişime karşı esnek yapıda. Ayrıca encodeType değişkeninden de kurtulduk. Dikkat ederseniz Main sınıfı içinde artık uygulamamıza Encoder interface’ni uygulayan sınıf User sınıfının yapıcı metodu kullanılarak veriliyor. Buna Dependency Injection deniyor. Bunu bizim için yapan birçok framework mevcut(Spring,PicoContainer,Castle Windsor) bunlarıda ayrı bir yazı altında inceleriz.Şimdilik benden bu kadar.Sorularınız, sorunlarınız, bütün görüşlerinizi yazabilirsiniz bunları duymaktan memnun olurum.

Single Responsibility Principle(SRP)

Evet lütfen kemerlerinizi bağlayın koltuklarınıza sıkı sıkı yapışın nesneye yönelik programlamanın özüne doğru yolculuğa çıktık.Ufak çaplı sarsıntı yaşayabilirsiniz.Verdiğimiz rahatsızlıktan dolayı özür dileriz. :)

Şimdi bu kadar abarttığıma bakmayın ama konunun önemini belirmek için böyle bir giriş yaptım.Nesneye yönelik programlamanın,tasarımın en temel  ve yazılım tasarımının en önemli prensiplerinden olan Single-Responsibility Principle (SRP) hakkında bilgi verip ufak bir örnekle incelemeye çalışacağız.

Nesneye yönelik programlamanın hatta genel programlama mantığının temelinde yatan metodları nesneleri düşünelim.Neden kullandığımız dillerde bu tarz yapılar mevcut?Yönetimi kolaylaştırmak için.Amaç farklı işleri yapan kavramları birbirinden metodlar, sınıflar kullanarak yönetilmesini kolaylaştırmak için birbirinden ayırmak.Nesneye yönelik programlamada bunu nesneler kullanarak, prosedürel programlamada ise fonksiyonlar fonksiyon modülleri kullanarak yapıyoruz.Ama temel mantık aynı farklı işleri farklı farklı sınıflar, metodlar, katmanlar, kütüphaneler içine koyarak ayırıyoruz.

Tabi bazen farklı farklı işleri gidip aynı sınıf,metod ya da modül içine koyabiliyoruz(biri bizi durdursun).Hatta çoğu zaman bunu bu şekilde yapıyoruz diyebilirim.Etrafta yüzlerce binlerce satırlık sınıflar, metodlar görmemizin sebebi de bu. Tabi böyle yapınca sınıfın kodunu aşağıya doğru çekince en son satıra ulaşmak 10 saniye alıyor neredeyse. Mesela gördüğüm bir sınıf yaklaşık 5000 satırdı aşağıya çekmem bile baya süre alıyordu malesef.

Bertrand Meyer Object-Oriented Software Construction kitabında sınıfların,fonksiyonların,modüllerin nasıl olması gerektiğini aşağıdaki cümleyle çok güzel özetlemiş.

A class has a single responsibility: it does it all, does it well, and does it only.
Classes, interfaces, functions, etc. all become large and bloated when they’re trying to do too many things.

Aslında Single-Responsibility Principle (SRP) tamamen yukarıdaki sözle özetlenebilir. Bir sınıf,fonksiyon vs.. sadece tek bir sorumluluğu yerine getirmelidir  ve yerine getirdiği sorumluluğu iyi yapmalıdır.Birden fazla sorumluluk yerine getirmeye çalıştığı zaman aşırı büyür ve karmaşıklaşır.

Bu prensip aslında yazılım dünyasında uzun zamandır bilinen temel kavramlardan biridir. Kısaca bu prensibi şöyle açıklayabiliriz, kodumuzda, tasarladığımız modüllerde kullandığımız sınıflar,uygulamadaki katmanlarımız,modüllerimiz sadece tek bir sorumluluğunu yerine getirmelidir. Yani sınıfın,modülün sadece kendi ile alakalı işleri yapmasıdır diyebiliriz. Çünkü birden fazla sorumluluğu yerine getiren sınıfların,modüllerin değişmesi için birden fazla neden vardır. Kendi ile alakalı şeyleri yapmayan sınıfların modüllerin aşağıdaki gibi dezavantajları vardır.

    Anlaması zordur Tekrar kullanılabilmesi zordur. Yönetilmesi zordur. Hassas ve sürekli olarak diğer değişikliklerden etkilenen yapıdadır.

Modüller açısından bu prensibi inceleyelim.Katmanlı mimari modüler tasarım modül vb.. kavramlar da aslında bu tasarım prensibinin uygulanmasının bir sonucudur.Örnek olarak 3 katmanlı bir yapıda tasarlanmış bir uygulamada klasik olarak Sunum(Presentation Layer), İş(Business Layer) ve Veri(Data Layer) katmanları bulunsun. Adlarından da anlayabileceğimiz gibi her katman kendine ait sorumlulukları yerine getirir. Biri(Data Layer) veritabanı ile ilgilenirken diğeri(Business Layer) iş kurallarıyla alakalı sorumlulukları yerine getirir. Bir diğeride(Presentation Layer) bilgilerin kullanıcılara sunulması sorumluluğunu yerine getirir. Bu şekilde tasarımın en önemli faydalarından biride bir katmandaki değişikliğin diğer katmanları etkilememesi ve iyi tasarlanmış katmanların tekrar başka uygulamalarda kullanılabilmesidir. Çünkü her katman sadece kendi ile alakalı tek bir sorumluluğu yerine getirir. Böylece değişmesi için tek bir sebep vardır.Bu da o katmandaki ihtiyaçların değişmesi. Örnek olarak sunum katmanındaki bir değişim veri katmanını etkilemez çünkü ikisi de birbirinden bağımsız ayrı sorumlulukları yerine getirirler.Tabi bu sözde anlatması kolay fakat gerçekleştirmesi bir o kadar zor bir kavramdır.

Sınıflar için baktığımızda bu prensip anlatması ve anlaşılması en kolay fakat uygulamaya gelince en çok zorlandığımız prensiplerden biridir.Mesela benim 5000 satırlık işçi için sınıfım işciler ile alakalı iş mantığını yönetmekle sorumlu diyebilirim.Ama bu yönetme içinde veritabanı ile iletişim,import,export özellikleri,iş mantığını,cache yönetimi…. gibi bir sürü ayrı sorumluluğu yerine getiriyor yani birden fazla işi yapıyor ama ben olaya öyle bakmıyorum.Bu yüzden sorumluluk kavramını tam anlamıyla doğru şekilde uygulamak zaman ve deneyim isteyen bir süreçtir. Bunun bazı sebeplerinden biride sorumluluk kavramının karıştırılabilmesi, biraz bulanık bir kavram olmasından da kaynaklanmaktadır.

Mesela bir sınıf için baktığımızda sorumluluk nedir ? Genelde sorumluluk denince sınıfın bir methodu akla gelir fakat gerçekte birden fazla method bir sorumluluğu yerine getiriyor olabilir. Bence sorumluluğun en güzel özeti bir sınıfın değişmesi için bir sebep olarak tanımlayabiliriz. Yani bir sınıfın,metodun.. değişmesi için birden fazla neden varsa o sınıfın birden fazla sorumluluğu yerine getirdiğini söyleyebiliriz.Mesela yukarıda bahsettiğim işçi sınıfı için söyleyecek olursak bu sınıf veritabanına erişim mantığı değiştiğinde değişebilir, ayrıca değişik bir import seçeneği sunmak istediğimizde yine sınıfı değiştirmek gerekir,yeni bir cache algoritması eklemek istediğimizde doğal olarak tekrar bu sınıfı değiştirmek zorunda kalacağız. Bu da bize sınıfın değişmesi için birçok sebep verdi demekki bu sınıfın çok fazla sorumluluğu var onun sırtındaki yükü biraz azaltmamız lazım.

Örneklerin genelde gerçek hayattan olmasına özen göstermeye çalışıyorum. O yüzden daha önceden üzerinde çalıştığım kodun küçük bir kısmını değiştirerek incelemek için aşağıya yazıyorum.

class Contact
{
    private int _contactID;

    public int ContactID
    {
        get { return _contactID; }
        set { _contactID = value; }
    }

    private string _name;

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

    private string _number;

    public string Number
    {
        get { return _number; }
        set { _number = value; }
    }

    private ContactType _type;

    public string Type
    {
        get { return _type; }
        set { _type = value; }
    }

    public int ImportExcel(File file)
    {
        CantactDao contactDao = null;
        List<Contact> contactList = null;
        int contactCount = 0;
        try
        {
            contactDao = new ContactDao();
            tr = contactDao.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);
            contactList = contactDao.GetContactsFromExcel(file);
            if (contactList != null &amp;&amp; contactList.Count > 0)
            {
                for (int i = 0; i < contactList.Count; i++)
                {
                    contactList[i].Type = ContactType.Friend;
                    contactList[i].Save();
                }
                contactDao.CommitTransaction(tr);
                contactCount = contactList.Count;
            }
        }
        catch (Exception ex)
        {
            contactDao.RollbackTransaction(tr);
        }
        return contactCount;
    }

    public int Save()
    {
        ContactDao contactDao = null;
        try
        {
            contactDao = daoFactory.GetContactDao();
            tr = contactDao.BeginTransaction();
            contactDao.Insert(this);
            contactDao.CommitTransaction(tr);
        }
        catch (Exception ex)
        {
            contactDao.RollbackTransaction(tr);
        }
        return this.ContactID;
    }
}
class ContactDao
{
    public void Insert(Contact contact)
    {
//veritabanı erişim kodları ile veritabanına Contact sınıfının eklenmesi 
    }

    public void Update(Contact contact)
    {
        //veritabanı erişim kodları ile Contact sınıfının güncellenmesi 
    }

    public void Delete(Contact contact)
    {
        //veritabanı erişim kodları ile sınıfın veritabanından silinmesi 
    }

    public Contact GetByID(int contactID)
    {
        //veritabanı erişim kodları ile sınıfın veritabanından alınması 
    }

    public List<Contact> GetContactsFromExcel(File file)
    {
        Contact contact = null;
        List<Contact> contactList = null;
        System.Data.OleDb.OleDbConnection connection = null;
        System.Data.OleDb.OleDbCommand selectCommand = null;
        try
        {
            string connectionString = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + File.FilePath +
                                      ";Extended Properties=Excel 8.0";
            string selectQuery = "SELECT * FROM [Contacts$]";
            connection = new System.Data.OleDb.OleDbConnection();
            connection.ConnectionString = connectionString;
            connection.Open();
            selectCommand = new System.Data.OleDb.OleDbCommand(selectQuery, connection);
            System.Data.OleDb.OleDbDataReader dataReader = selectCommand.ExecuteReader();
            if (dataReader != null &amp;&amp; dataReader.HasRows)
            {
                contactList = new List<Contact>();
                while (dataReader.Read())
                {
                    contact = new Contact();
                    contact.Name = dataReader["Name"];
                    contact.Number = dataReader["Number"];
                    //....diğer alanların alınması ve kontrolü 
                    contactList.Add(contact);
                }
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        return contactList;
    }
}

Şimdi yukarıdaki kodun yapısını biraz inceleyelim. Bir SMS programımız var SMS programının özelliklerinden biri dışarıdan excel içindeki kişi listesini programımıza import edebiliyoruz.Genelde çoğu programda bu tarz özellikleri sizde kullanmıştırsınız.Kodda gördüğünüz gibi bir adet Contact sınıfımız var bu sınıf programımızda kullandığımız kişileri temsil ediyor.Bu sınıfın metodlarına bakarsak gördüğünüz gibi bir adet excel dosyasından import eden ImportExcel metodumuz var. Ayrıca veritabanına bilgileri kayıt eden bir adet Save metodumuz var.Yani ActiveRecord tasarım kalıbını uygulayarak kendini veritabanına kayıt ediyor. ContactDao adında DAO(Data Access Object) tasarım kalıbını uygulayan bir adet sınıfımız var.Bu sınıfımızda Contact sınıfımızı veritabanına ekleme,güncelleme, silme ve Excel dosyasından veri alma işlemini yapıyor. Şimdide UML olarak static sınıf diyagramına bakalım.


Yukarıdaki Uml diyagramını çizmemin tek amacı görsel olarak sınıflar arasındaki ilişkileri görmek kodu okumaktan daha kolay olmasıdır. Şimdi yukarıdaki diyagram yardımıyla sınıfları daha yakından incelemeye başlayalım.Öncelikle sınıfların birbirine olan bağımlılığına(Dependency) bakalım. Etrafta uçuşan okları görüyorsunuz görüntü pek iç açıcı değil açıkçası. Contact sınıfımız üzerindeki ImportExcel metodu sayesinde File sınıfına bağımlı. Ayrıca Save metodunu kullandığında da Transaction oluşturduğu ve CantactDao sınıfının metodunu çağırdığı için bu sınıfa ve ADO.NET sınıflarına da bağımlı.

Öncelikle kötülüklerin anası olan bağımlılık neden bu kadar kötü kısaca bahsedelim. :) Bağımlılığı fazla olan sınıfları yeniden kullanmanız (Reuse) çok zordur. Çünkü kullanmak istediğiniz sınıf diğer  birçok sınıfa bağımlıdır onunla birlikte diğer sınıflarıda projede dahil etmeniz gerekir. Tabi bu da o kadar kolay değildir.Çünkü diğer sınıflarında birçok diğer sınıfa bağımlı olduğu düşünürsek küçük bir sınıfı yeniden kullanmak istediğinizde bütün projeyi diğer projeye dahil etmeniz gerekir. Diğer bir kötü yanı bağımlı olduğu sınıflarda meydana gelen hataların o sınıfı kolaylıkla etkilemesidir. Bu konuda daha geniş bilgi için Dependency Inversion prensibine bakabilirsiniz.Ayrıca gördüğünüz gibi Contact sınıfı,ContactDao sınıfı kendi ile alakalı olmayan metodlar ile gittikçe büyüyor. Sınıfların büyüdükçe yönetilmesinin çok zor olduğunu biliyorsunuz. Benim gibi 5000 satırlık kodun içinde değişiklik yapmanın hatanın ne olduğunu bulmanın nasıl bir kabul olduğunu bilirsiniz.

Şimdi bu kadar şeyden bahsettik bunların Single Responsibility Principle ile alakasının ne olduğunu merak etmiş olabilirsiniz haklı olarak. Kısaca burada ki bağımlılığın sınıfların gittikçe büyümesinin sebebi sınıfların birden çok sorumluluğunun olmasıdır. Contact sınıfımız hem sistemimizde bir kavramı temsil ederken hemde onu sisteme import etme işlemini yapıyor. Ayrıca ContactDao sınıfımız hem veritabanı ile alakalı Insert,Update… gibi işlemleri yaparken ayrıca Excel dosyasından kayıtları okuma işlemini yapıyor. Bu sınıfın değişmesi için birçok neden sıralayabiliriz.Import mantığımız değişir, Contact sınıfını değiştiririz. İş mantığımız değişir, Contact sınıfını değiştiririz. Excel dosyasından okuma mantığımız değişir, ContactDao sınıfımız değişir.Kullanılan teknolojiyi ADO.NET yerine başka birşey kullanmak isteriz iki sınıfımızda değişmek zorunda kalır.Yani değişime karşı sınıflarımız oldukça kırılgan yapıdalar.

Şimdi fazla sorumluluğu olan sınıfların yüklerini biraz hafifletelim. Bunu yapmak için bütün yapılan sorumlulukları liste halinde yazalım.Bu listeyi sorumlulukları atamada kullanacağız.

    Kişilerin sistemnde temsil edilmesi (Contact sınıfı) Kişilerin veritabanı ile alakalı işlerin yapılması (ContactDao sınıfı) Kişilerin import edilerek sisteme kayıt edilmesi (Contact sınıfı) Excel dosyasından satırların okunarak kişi nesnesine çevrilmesi (ContactDao sınıfı)

Gördüğünüz gibi 4 tane sorumluluk 2 tane sınıf arasında paylaştırılmış. Biz kodumuzu değiştirerek her sorumluluğu tek bir sınıfa atayacağız yani Single Responsibility prensibini uygulayacağız. Bunun için öncelikle bu sorumluluklara iyi birer isim bulmaya çalışalım. Dikkat edin bunu sezgi yoluyla yapıyorum.

    Öncelikle kişilerin sistemde temsil edilmesi Contact sınıf bu aynen kalacak. Kişilerin veritabanı ile alakalı işlerinin yapılması zaten bu işi yapan sınıfımız var ContactDao adı üzerinde Data Access Object Kişilerin import edilmesi buna ContactImporter diyelim Kişilerin Excel dosyasından okunması buna da ContactExcelReader olsun.

Sınıflarımızın adlarını belirledikten sonra fazla sorumluluğu olan sınıflardan kodların yeni sınıflara taşınması işlemi var. Bu işlemi yaparken tabiki kes-yapıştır yapmanızı tavsiye etmiyorum. Birçok istenmeyen hata ile karşılaşabilirsiniz. Bunu Refactoring, ve Unit Testing desteğiyle en güvenli şekilde yapabilirsiniz. Konumuzu fazla dağıtmamak için değinmiyorum amacımız Single Responsibility Prensibini açıklamak.O yüzden benim şuanda kes-yapıştır yapmama fazla aldırmayın.Lafı fazla uzatmadan yeni kodlarımızı aşağıya yazalım.

internal class Contact
{
    private int _contactID;

    public int ContactID
    {
        get { return _contactID; }
        set { _contactID = value; }
    }

    private string _name;

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

    private string _number;

    public string Number
    {
        get { return _number; }
        set { _number = value; }
    }

    private ContactType _type;

    public string Type
    {
        get { return _type; }
        set { _type = value; }
    }
}
class ContactImporter
{
    private ContactDao contactDao = new ContactDao();
    private CntactExcelReader contactReader = new ContactExcelReader();

    public int ImportFromExcel(File file)
    {
        List<Contact> contactList = null;
        int contactCount = 0;
        try
        {
            tr = contactDao.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);
            contactList = contonctReader.GetContactsFromExcel(file);

            if (contactList != null &amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp; contactList.Count > 0)
            {
                for (int i = 0; i < contactList.Count; i++)
                {
                    contactList[i].Type = ContactType.Friend;
                    contactList[i].Save();
                }

                contactDao.CommitTransaction(tr);
                contactCount = contactList.Count;
            }
        }
        catch (Exception ex)
        {
            contactDao.RollbackTransaction(tr);
        }
        return contactCount;
    }
}
class ContactDao
{
    public int Save(Contact contact)
    {
        try
        {
            Transaction tr = this.BeginTransaction();
            this.Insert(contact);
            this.CommitTransaction(tr);
        }
        catch (Exception ex)
        {
            this.RollbackTransaction(tr);
        }

        return contact.ContactID;
    }

    public void Insert(Contact contact)
    {
//veritabanı erişim kodları ile veritabanına Contact sınıfının eklenmesi
    }

    public void Update(Contact contact)
    {
//veritabanı erişim kodları ile Contact sınıfının güncellenmesi
    }

    public void Delete(Contact contact)
    {
//veritabanı erişim kodları ile sınıfın veritabanından silinmesi
    }

    public Contact GetByID(int contactID)
    {
//veritabanı erişim kodları ile sınıfın veritabanından alınması
    }
}
class ContactExcelReader
{
    private GetContactsFromExcel(File file)
    {
        Contact contact = null;
        IList<Contact> contactList = null;
        System.Data.OleDb.OleDbConnection connection = null;
        System.Data.OleDb.OleDbCommand selectCommand = null;

        try
        {
            string connectionString = @"Provider = Microsoft.Jet.OLEDB.4.0; Data Source =" + File.FilePath +
                                      ";Extended Properties = Excel 8.0";

            string selectQuery = "SELECT*FROM[Contacts$]";

            connection = new System.Data.OleDb.OleDbConnection();
            connection.ConnectionString = connectionString;
            connection.Open();
            selectCommand = new System.Data.OleDb.OleDbCommand(selectQuery, connection);
            System.Data.OleDb.OleDbDataReader dataReader = selectCommand.ExecuteReader();
            if (dataReader != null &amp;amp;&amp;amp; dataReader.HasRows)
            {
                contactList = new List<Contact>();

                while (dataReader.Read())
                {
                    contact = new Contact();
                    contact.Name = dataReader["Name"];
                    contact.Number = dataReader["Number"];
                    //....diğer alanların alınması ve kontrolü
                    contactList.Add(contact);
                }
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }

        return contactList;
    }
}

Yeni sınıflarımıza bir de UML diyagramı ile bakalım.

Şimdi yukarıda ki diyagram yadımıyla kodumuzun son haline bakalım. Gördüğünüz gibi artık Contact sınıfımız ne File,ContactDao gibi sınıflara ne de herhangi bir ADO.NET teknolojisine bağımlı.Fazla sorumluluğu üzerinden alınca diğer sınıflardan bağımsız hale geldi. Diğer sınıflara baktığımızda sadece kendi işlerini yapan ve gereksiz bağımlılıklardan kurtulmuş sınıflar var.ContactExcelReader sadece Excel dosyasından Contact okuma işini yapıyor,ContactImporter liste halinde verilen kişileri veritabanına import ediyor. ContactDao sadece veritabanı ile alakalı işlemleri yapıyor.Artık sınıfların değişmesi için tek sebep var. Kodun anlaşılması ve yönetilebilmesi daha kolay.

Single Responsibility prensibini her zaman uygulamak çok faydalı olmayabilir. Mesela çok küçük bir projede uygulamak sınıf sayısını arttırdığı için sistem daha komplex olur.Mesela yukarıda ActiveRecord tasarım kalıbını uygulayan Save metodunu gidip ContactDao sınıfı üzerine taşıdık.Fakat bu tasarım kalıbı küçük çaplı, iş mantığının yoğun olmadığı çoğu projede başarı kullanılabiliyor.Ama çoğu durumda uygulamanız için  bu prensibi uygulamak uygulamanın yönetimi bakımı hata oranı için oldukça önemli rol taşıyor.

Command-Query Separation Principle

Nesneye yönelik programlamanın önemli prensiplerinden olan Command Query Seperation Prensibini küçük bir örnekle inceleyeceğiz.Bu konuda yine örnekleri gerçek dünyadan olsun diye eski kodlarımı baya bir karıştırdım. Eski kodlarda her türlü bad smell bulabiliyorum ne mutlu bana :) Öncelikle bu prensibi ihlal eden aşağıdaki kodumuza bakalım.Bunu C# da Property olarak yazmışım. Java içinde aynı kodu getter olarak yazabiliriz. İki koduda aşağıda görüyorsunuz.

C# Versiyonu

<br />
public Resim KapakResim<br />
{<br />
 get<br />
  {<br />
  if(kapakResim!=null)<br />
   return kapakResim;<br />
  else<br />
  {<br />
   bosResim.KayitYolu =bosResim.ServerDizini &quot;yok.gif&quot;;<br />
   bosResim.Kaydet();</p>
<p>   return null;<br />
  }</p>
<p> }<br />
}<br />

Java Versiyonu

<br />
public Resim getKapakResim()<br />
 {<br />
 if (kapakResim != null)<br />
 return kapakResim;<br />
 else<br />
 {<br />
 bosResim.KayitYolu = bosResim.ServerDizini &quot;yok.gif&quot;;<br />
 bosResim.Kaydet();</p>
<p> return null;<br />
 }<br />
 }<br />

Şimdi yukarıdaki koda baktığınızda hiçbir problem görmüyor olabilirsiniz. İlk başlarda bende görmüyorum :) Buradaki problem şöyle açıklayalım. Dışarıdan birisi sizin yukarıdaki gibi yazdığınız kodu alıp kullandığında masum bir fikirle aşağıdaki gibi kodunun içinde kullanacak

  • C# da Resim resim =resimlistes.KapakResim
  • Java da Resim resim=resimListesi.getKapakResim();

Sınıfı kullanan bunun basit bir get metodu olduğunu yani sadece bir nesneyi bize geri döndürdüğünü düşünüyor fakat arka planda boş resim oluşturup dosya olarak kayıt ettiğinden haberi yok.Yani kullanıcı sadece yan etkisiz bir nesne almayı beklerken bu getter metodları nesnenin iç yapısında değişiklikler yapıp çeşitli işlemler yapıyor. Yani kodun anlaşılabilirliği oldukça düşük. Çünkü siz getKapakResim dediğinizde içeride boş resim oluşturup kayıt ettinden haberiniz olmuyor. Buda hata yapmaya oldukça açık bir yapı.

Bu yüzden ilk olarak bu prensibi Bertrand Meyer ortaya atıyor.Ne demiş şöyle özetleyelim. Nesnelere ait metodlar anlaşılabilir bir kod yapısı için şu şekilde olmalıdır.

  • Query Metod : Nesnenin içinde hiçbir değişiklik yapmadan hiç bir yan etkisi olmadan sadece geri dönüş tipinde bir nesne geri döndürür.(nesne.getResim(),nesne.Resim….gibi)
  • Command Metod : Nesnenin içinde çeşitli işlemler yapan durumunu değiştiren genelde dönüş tipi void olan metodlardır. (nesne.kaydet(),nesne.yap()…gibi)

Şimdi yukarıdaki koda baktığımızda problemi casino

online daha iyi görebiliyoruz.Bu kodda nesne.KapakResim yada nesne.getKapakResim() çağırıldığında hem nesnenin durumunda değişiklikler yapılıyor hemde geri bir nesne dönülüyor. Yani Command ve Query aynı metod içinde yapılmış. Bu metodu aşağıdaki aşağıdaki gibi Command ve Query olarak ikiye ayırırsak kodun anlaşılabilirliği daha da artacaktır.

<br />
 public Resim getKapakResim()<br />
 {<br />
 return kapakResim;<br />
 }</p>
<p> public void bosResimKaydet()<br />
 {<br />
 bosResim.KayitYolu = bosResim.ServerDizini &quot;yok.gif&quot;;<br />
 bosResim.Kaydet();<br />
 }<br />

Yukarıdaki gibi kodumuzu ikiye ayırdık.Yani Query metodu getKapakResim sadece hiçbirşey yapmadan geri resim döndürüyor. Diğer Command metodu olan bosResimKaydet metodu ise nesnenin içinde çeşitli işlemler yapıyor. Tabi üstteki if kontrolü nereye gitti diyebilirsiniz. Dışarıda kodu kullanan da şu şekilde kodu kullanıyor.

<br />
 public void arabaKaydet()<br />
 {<br />
 if (resimListesi.KapakResim == null)<br />
 resimListesi.bosResimKaydet();<br />
 else<br />
 Resim kapakResim = resimListesi.gerKapakResim();<br />
 }<br />

Kısaca özetlersek bu prensibe uymanız kodunuzun hata oranını azaltacak anlaşılabilirliğini oldukça arttıracaktır. Tabi herzaman mutlaka uyacaksınız diye bir şey yok ama çoğu durumda metodları Command ve Query olarak ayırmak oldukça faydalı..Prensiplere uyalım uymayanları uyaralım :)

Dependency Inversion Principle (DIP)

Merhaba bu makalede sizlere nesneye yönelik yazılım tasarımının temel prensiplerinden olan Dependency Inversion Principle(DIP) hakkında bilgi verip ufak bir örnekler anlatmaya çalışacağım.Türkçe olarak ifade etmeye çalışırsak buna bağımlılığın ters çevrilmesi diyebiliriz.

Klasik prosedürel programlamada stilinde geliştirilen yazılımlarda genellikle üst seviyeli modüller, sınıflar vb.. alt seviyeli modüllere bağımlıdır. Bunun en önemli dezavantajlarından biriside alt seviyeli modüller ya da sınıflar daha sık değişebilir ve de bu değişimden de üst seviyeli modüller etkilenir. Bu değişimin sonucunda üst seviyeli modüllerde kod içerisinde değişiklik yapmak ve zincirmele olarak tüm modülleri derleyip yayınlamak gerekir. Sonuçta ufak değişikliklerden bile en üst seviyeye kadar etkilenebilen yönetilmesi zor bir model oluşur. Bu tarz modeller modüler olarak tekrar kullanılabilir gözüksede üst seviyeden alt seviyeye kadar bağımlılık zinciri olduğu için tekrar kullanmak istediğimiz modülle birlikte diğer modülleri de dahil etmemiz gerekir.Bu da bize gerçek anlamda bir tekrar kullanılabilirlik sağlamaz çünkü gerçek anlamda birbirinden soyutlanmış modüller yoktur. Malesef günümüzde modern birçok nesneye yönelik programlama dilinde dahi bu şekilde yazılım geliştirilmektedir çünkü kullandığımız programlama dilinin nesneye yönelik olması bizim nesneye yönelik yazılım geliştireceğimiz anlamına gelmez. Aşağıda örnek olarak prosedürel yöntem ve Dependency Inversion Principle(DIP) yöntemi ile tasarlanmış modelleri görebiliriz.

ProceduralLayers DIPLayer

Yazılım geliştirmede genellikle Tekrar Kullanılabilirlik ve Esneklik terimleri sıkça duyarız. Peki gerçekten bu terimler bize ne ifade eder? Tekrar kullanılabilir ve esnek yazılımları nasıl geliştirebiliriz ?Bu gibi soruların cevabı profosyonel yazılım geliştirmede oldukça önemlidir. Önce bağımlılık ve tekrar kullanılabilirlik ile başlayalım.

Bağımlılık teriminide kısaca özetlersek bir sınıfın başka bir sınıfa bağımlı olması yani bir sınıfın çalışması için başka bir sınıfa ihtiyaç duymasıdır diye açıklayabiliriz Yandaki şekilde gördüğümüz gibi üst seviyeli bir katman alt seviyeli katmanı (sınıfları,arayüzler… olabilir ) kullandığı için burada üst seviyeli katmanımız alt seviyeli katmana bağımlı diyebiliriz. Tekrar kullanılabilirlik terimine gelince; ilk olarak bir sınıfın yazılıp projenin çeşitli yerlerinde kullanılması aklımıza gelir fakat gerçek anlamda tekrar kullanım yazdığımız bir kod, üst seviyeli bir kütüphane diğer projelerde koduna dokunulmadan kullanılabiliyosa bunu tekrar kullanılabilir modül ya da yazılım olarak tanımlayabiliriz. Bunuda iyi tasarlanmamış yazılımlarda sınıfların birbirine olan bağımlılığı yüzünden genellikle yapamayız.

İşte bu noktada daha esnek ve yeniden kullanılabilir modüller geliştirebilmek için uygulanması gereken önemli bir yazılım prensibi olan Dependency Inversion Principle(DIP)(Bağımlılığın Ters çevrilmesi) devreye giriyor. Bu yöntemi kısaca açıklarsak;

Üst seviyeli modüller,sınıflar vb.. alt seviyeli sınıflar, modüllere bağımlı olmamalıdır. Alt seviyeli modüller üst seviyeli modüllere(modüllerin arayüzlerine) bağımlı olmalıdır. Buna kısaca Dependency Inversion (bağımlılığın ters çevrilmesi) deriz.

Bunu da şekilde gördüğümüz gibi üst seviyeli bir modül kendi arayüzünü tanımlayarak bu arayüzü kullanır. Bu arayüz abstract ya da interface sınıflar olabilir. Ardından alt seviyeli modüller üst seviyeli modülün arayüzünü uygular. Böylece üst seviyeli modülümüz alt seviyeli detaylarla ilgilenmeden onlardanki değişimlerden etkilenmeden kullanılabilir. Üst seviyeli modülümüz bir arayüze bağımlı olduğu için ve arayüzde genellikle değişmeyeceği için arayüzü gerçekleyen (implemention) sınıflar değişse dahi aynı kalacaktır. Günümüzdeki frameworklerin tasarlanmasında kullanılan temel prensiplerden biri budur.

Bu kadar laf kalabalığından sonra biraz sıkılmış olabilirsiniz.Ufak bir örnekle sıkıntınızı alıp işe koyulalım:) Örnek senaryomuz şöyle olsun:

Geliştirdiğimiz bir yazılımda işyerimizle alakalı kayıtları işliyoruz. Programımızın işyerindeki çalışanlara ait çeşitli formatlarda rapor sunması gerekiyor. Şuanda bizden istenen excel ve word formatından raporları sunması. Fakat ileride değişik türden formatta raporlarıda kolaylıkla sunması kesinlikle istenecektir. Unutmayın müşteri istekleri bitmez:) Bu işlemleri yaptığımız bir çalışan raporları sayfamız var. Bu sayfadan herhangi bir çalışanı seçip onla alakalı çeşitli formattaki raporları alabiliyoruz. Bu isteklere göre tasarladığımız sınıflarımızın UML diyagramını aşağıda görebiliriz.

Dependency

Yukarıdaki şekilde de gördüğümüz gibi Calisan rapor forumumuz CalisanRaporFormu sınıfı üst seviliyeli bir işi yapan sınıftır. CalisanWordRaporu ve CalisanExcelRaporu ise bu üst seviyeli işlemin nasıl yapılacağını değişik şekillerde uygulayan alt seviyeli detaylarla ilgilenen sınıflardır. CalisanRaporFormu bu sınıfları direkt olarak kullanır. Yani CalisanRaporFormu sınıfı alt seviyeli sınıflara direk olarak bağımlıdır onlarda meydana gelecek değişimlerden etkilenecek ve tekrar derlenmek zorunda kalacaktır. Bu şekilde tasarlanmış yazılımlığımızın kodları kabaca aşağıdaki gibi olur. Kodlara baktığımızda ilk olarak if else yapısı dikkatinizi çekmiştir. Genellikle kodunuzda buna benzer kısımları görürseniz büyük ihtimalle refactoring(yeniden biçimlendirme) yapmanızda fayda vardır. İyi şekilde tasarlanmış yazılımlarda genellikle if-else ve switch-case cümleleri kodun içinde fazla bulunmaz. Bunu da aklımızda bulundurmamızda fayda var. Aşağıda buna örnek bir dizi sınıf tasarımı verilmiştir.

public class CalisanRaporFormu {
        private List calisanlar;
        private Calisan seciliCalisan;
        private CalisanWordRaporu wordRaporu =new CalisanWordRaporu();
        private CalisanExcelRaporu excelRaporu =new CalisanExcelRaporu();

        public CalisanRaporFormu(){ }

        public void SeciliCalisanaAitRapor()
        {
                if(raporTipi=="Word")
                        wordRaporu.RaporOlustur(seciliCalisan);
                else if(raporTipi=="Word")
                        excelRaporu.RaporOlustur(seciliCalisan);
        }
}
public class Calisan {
        private string adi;
        private int maas;
        private string soyadi;
        private int mesaiSaati;
        public Sirket m_Sirket;

        public Calisan(){ }

        public int MesaiUcretiHesapla(){ }

        public string adi{
                get{return adi;}
                set{adi = value;}
         }

         public int maas{
                get{return maas;}
                set{maas = value;
         }
}
public class CalisanWordRaporu {
      public CalisanWordRaporu(){

      }

       public void RaporOlustur(Calisan calisan){
            //raporlama ile alakalı kodlar....
      }
}

public class CalisanExcelRaporu {
      public CalisanExcelRaporu(){

      }

       public void RaporOlustur(Calisan calisan){
            //raporlama ile alakalı kodlar....
      }
}

Yukarıdaki kodlar senoryamuzdaki bizden istenenleri gerçekleştiriyor. Müşterimide yazılımı teslim ettik herşey gayet güzel çalışıyor. Fakat beklenen kara gün geldi müşteri yeni isteklerle karşımıza çıktı:) Bunlardan senaryomuzda biraz da olsa bahsetmiştik.Bunlardan birtanesi senaryomuzda bizden istenen değişik formatta rapor çıktısı sunmasıydı ve müşteri bizden Pdf formatındaki raporları da sisteme eklememizi söyledi. Fakat bundan sonra yapılacak değişimler için önümüze birkaç problem çıkıyor. Yeni bir çalışan rapor formatını sisteme nasıl ekleriz? Bunun için eski kodlarımızın içini didiklemeye başladık ve bu işlemin CalisanRaporFormu sınıfı içinde yapıldığını gördük. Hemen kolları sıvadık başladık yeni rapor formatını sistemimize eklemeye.Sonra bütün kodlarımızı tekrar derleyip yeni halini müşterimize gönderdik.Değişin CalisanRaporFormu sınıfımız ve yeni eklenen Pdf raporu sınıfımız aşağıdaki şekilde olacaktır.

public class CalisanRaporFormu {
        private List calisanlar;
        private Calisan seciliCalisan;
        private CalisanWordRaporu wordRaporu =new CalisanWordRaporu();
        private CalisanExcelRaporu excelRaporu =new CalisanExcelRaporu();

        private CalisanPdfRaporu pdfRaporu =new CalisanPdfRaporu();

        public CalisanRaporFormu(){ }

        public void SeciliCalisanaAitRapor()
        {
                if(raporTipi=="Word")
                        wordRaporu.RaporOlustur(seciliCalisan);
                else if(raporTipi=="Word")
                        excelRaporu.RaporOlustur(seciliCalisan);
                else if(raporTipi=="Pdf")
                        pdfRaporu.RaporOlustur(seciliCalisan);
        }
}
public class CalisanPdfRaporu {
      public CalisanPdfRaporu(){

      }

       public void RaporOlustur(Calisan calisan){
            //raporlama ile alakalı kodlar....
      }
}

Yukarıda gördüğümüz gibi ufak bir değişiklik sistemin birçok yerinde değişiklik yapmamızı mecbur kılıyor ve bütün projeyi derleyip tekrar yayınlamamızı gerektiriyor. Kaldı ki müşterinin istekleri hiç bitmeyeceği için:) her yeni rapor formatında ya da programın değişik yerlerinde bu şekilde istekler için tekrar aynı işlemleri yapmak zorunda kalacağız en azından yukarıdaki gibi if-else cümleleri kodumuzu istila edecek bu da yazılımın yönetimini, yayınlanmasını, değişiklik eklemeyi ve yeniden kullanımız zorlaştıracak. Sistemimizi birde Dependency Inversion prensibine göre baştan tasarlayalım ve ardından bize sağladığı avantajlara bakalalım. Bunu yapmak için baştada bahsettiğimiz gibi bağımlılığı ters çevireceğiz. Yani CalisanRaporFormu sınıfımız alt raporlama sınıflarına bağımlı olmayacak kendi arayüzünü tanımlayacak ve yapacağı işlemlerde onu kullanacak. Ardından alt seviye sınıflar bu arayüzü uygulayarak CalisanRaporFormu sınıfımızın arayüzüne bağımlı olacak. Yeniden tasarlanmış sınıflarımızın UML diyagramı ve kodları aşağıdaki gibi olur.

DIPYeniden

public class CalisanPdfRaporu : ICalisanRaporu{
      public CalisanPdfRaporu(){

      }

       public void RaporOlustur(Calisan calisan){
            //raporlama ile alakalı kodlar....
      }
}

public class CalisanExcelRaporu : ICalisanRaporu{
      public CalisanExcelRaporu(){

      }

       public void RaporOlustur(Calisan calisan){
            //raporlama ile alakalı kodlar....
      }
}

public class CalisanWordRaporu : ICalisanRaporu{
      public CalisanWordRaporu(){

      }

       public void RaporOlustur(Calisan calisan){
            //raporlama ile alakalı kodlar....
      }
}
public class CalisanRaporFormu {
        private List calisanlar;
        private Calisan seciliCalisan;
        private ICalisanRaporu calisanRaporu;

        public CalisanRaporFormu(ICalisanRaporu calisanRaporu)
       {
               this.calisanRaporu = calisanRaporu;
       }

        public void SeciliCalisanaAitRapor()
        {
                this.calisanRaporu.RaporOlustur(seciliCalisan);
        }
}

Yukarıda yeni şekilde tasarladığımız kodlarda da gördüğümüz gibi artık üst seviyeli bir sınıfımız olan CalisanRaporFormu sınıfımız alt seviyeli sınıflardan olan CalisanPdfRaporu vb.. sınıflara bağımlı değildir, sadece kendi arayüzünü tanımlamış ve işlemlerinde bu arayüzü kullanmıştır. Alt seviyeli sınıflar ise CalisanRaporFormu arayüzü olan ICalisanRaporu arayüzünü CalisanRaporFormu sınıfına, onun kullandığı arayüze bağımlı olmuşlardır. Bu şekilde bağımlılığı ters çevirmiş olduk.Yukarıdaki kodları gördüğünüzde aklınıza CalisanRaporFormu sınıfının hangi rapor formatı ile çalışacağını nerden bileceği gelebilir. Bunu gördüğünüz gibi CalisanRaporFormu yapıcısında hangi rapor formatı ile çalıştığını alıyor. Genelde bu tür işlemler herbiri ayrı bir makale konusu olan Factory Tasarım Kalıbı ve Inversion Of Control (IoC) denilen tekniklerle yapılır. Şuanda kafanız o kısımda karışmasın diye deyinmek istemedim ayrı bir makalede bu konulara deyinmeye çalışacağım. Yeni şekilde tasarlanan kodun avantajlarına bakarsak gördüğümüz gibi CalisanRaporFormu artık kendi arayüzü uygulayan hangi tür rapor olursa olsun kod içinde hiçbir değişiklik yapmadan kullanabileceğiz. Tabi kodun içindeki if-else ifadeleri kalktığı için kodunda sadeleştiğini görüyoruz. Ayrıca bundan sonraki yeni rapor türleri için CalisanRaporFormu sınıfını tekrar derlemek zorunda değiliz çünkü bu sınıf ICalisanRaporu arayüzünü kullanıyor ve bu arayüz değişmedikçe tekrar dermemize gerek kalmayacak. Müşteriye sadece yeni eklediğimiz rapor formatı sınıflarını vermemiz yeterli olacaktır. Artık CalisanRaporFormu bizim için ayrı bir modül oldu. Değişik projelerde onun arayüzünü uygulayan herhangi bir sınıfla rahatlıkla çalışabilir. Tabi onu kütüphane yapıp aynı namespace içine ICalisanRaporu arayüzünüde eklemeyi unutmayalım. Paket yapımızda aşağıdaki gibi olur. Artık müşteride bulunan CalisanRaporu paketi(kütüphane,dll) yeni rapor formatı ekleme işlemleri için değişmeyecektir. Sadece RaporFormarlari paketine yeni rapor formatını ekleyerek tekrar derleyip müşteriye vermemiz yeterli olacaktır.

DIPPackage

Gördüğünüz gibi ufak bir örnekle de olsa önemli bir tasarım prensibinin bize sağladığı avantajları oldukça önemli. Yeniden kullanılabilir modüller ve framework’ler tasarlamanın temel prensiplerinden biri olan bu tasarım prenbini ufak bir örnekler inceledik umarım sizler için faydalı olmuştur. Tekrar görüşmek dileğiyle…

Command Pattern

Favori Design Pattern’larımdan biri olan Command Pattern nedir, ne işe yarar, hangi durumlarda kullanılır beraberce bakalım.Genelde yaygın olarak kullanılan Command Pattern oldukça faydalı kullanım alanlarına sahiptir. Çoğumuz kullanırken ne olduğunu farkemesek bile .NET, Java .. birçok dilde,kütüphanede uygulanmış bu kalıbı kullanırız. Bu tasarım kalıbının temel amacı değişen method çağırımlarını(method invocation) kodun diğer kısmından soyutlamakdır. Tarif biraz karmaşık oldu farkındayım. Bu yüzden hemen nerelerde ve nasıl kullanıldığına bakalım.

Mesela Java Swing kullananların bildiği AbstractAction sınıfları bir Command Pattern örneğidir.Birde kullanımına bakalım. Çok basit bir formumuz olsun içinde sadece tek bir buton nesnemiz olsun.Bu butonun içinde yapılan şeyler değişiklik gösteriyor.Bazen butona basıldığında Bize Merhaba mesajı göstermesini istiyoruz bazende formumuzu kapatmasını istiyoruz.Bu tarz bir gereksinimi Command Pattern kullanmadan klasik olarak buton kodunun içine if-else yazarak aşağıdaki gibi yapabilirdik.

public class CommandFrame extends JFrame {
    private JButton buton;
    private final String komutTipi;
    public CommandFrame(String komutTipi){
        this.komutTipi =komutTipi;
        setTitle("Command Pattern");
        setSize(300,200);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        initButton();
    }

    private void initButton() {
        buton =new JButton("Calistir");
        buton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if(komutTipi.equals("Mesaj")){
                    JOptionPane.showMessageDialog(CommandFrame.this,"Merhaba");
                }else if(komutTipi.equals("Kapat")){
                    CommandFrame.this.dispose();
                }
            }
        });
        getContentPane().add(buton);
    }

    public static void main(String[] args) {
        CommandFrame frame =new CommandFrame("Mesaj");
        frame.setVisible(true);
    }
}


Gördüğünüz gibi yukarıdaki sınıfımızda if-else kontrolü yaparak her defasında kontrol yapıp neyin çalışması gerektiğini buluyoruz ve ardından onu çalıştırıyoruz. Tabi çalışması gereken şeyler bu kadar basit olmayabilir.Mesela kullanıcıdan yeni bir ihtiyaç geldi ve o butona basıldığında veritabanına yeni bir çalıştırıldı kaydı eklememizi istiyor.Peki ne yapacağız? İlk aklımıza gelen bir if-else daha ekleyip o if-else içine veritabanına bağlanıp kayıt ekleyen kodu aşağıdaki gibi yazmak olacaktır.

public class CommandFrame extends JFrame {
    private JButton buton;
    private final String komutTipi;
    private Connection con;
    private Statement stmt;

    public CommandFrame(String komutTipi) {
        this.komutTipi = komutTipi;
        setTitle("Command Pattern");
        setSize(300, 200);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        initButton();
    }

    private void initButton() {
        buton = new JButton("Calistir");
        buton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (komutTipi.equals("Mesaj")) {
                    JOptionPane.showMessageDialog(CommandFrame.this, "Merhaba");
                } else if (komutTipi.equals("Kapat")) {
                    CommandFrame.this.dispose();
                } else if (komutTipi.equals("Kayit")) {
                    try {
                        Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                        con = DriverManager.getConnection("jdbc:odbc:Bus", "", "");
                        stmt = con.createStatement();
                        stmt.executeUpdate("Insert Into Tablo (Deger) VALUES ('Calisti')");

                        stmt.close();
                        con.close();
                    } catch (ClassNotFoundException e1) {
                        e1.printStackTrace();
                    } catch (SQLException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        });
        getContentPane().add(buton);
    }

    public static void main(String[] args) {
        CommandFrame frame = new CommandFrame("Kayit");
        frame.setVisible(true);
    }
}

Yukarıdaki CommandFrame sınıfına bakarsanız içinde yok yok olduğunu görecektiniz. Swing sınıflarından,JDBC kodlarına kadar herşeyi yazdık içine.Tabi artık bu sınıfımız birçok diğer sınıfa bağımlı hale geldi.Kodun karmaşıklığı arttı ayrıca diğer kısımlarda CommandFrame sınıfımıza kullanmak neredeyse imkansızlaştı. Çünkü her yeni eklenecek özellikte bu sınıfa yeni bir if-else yapısı ekleyip tekrar derlemek zorundayız.Burada hemen Command Pattern yardımımıza koşuyor.Zaten koda biraz üstten baktığımızda if-else yapıları içinde farklı olan şeylerin birşeyleri çalıştırmak olduğunu göreceksiniz.Aslında hepsinde farklı işler yapan bir metod çağırılıyor diyebiliriz. Command Pattern’ın özüde bu zaten. Her birinde farklı işler yapan o metodları koddan soyutlamak. Bunu Java’da interface kullanarak,.NET de delegate kullanarak yapabiliriz. Zaten bunun için Swing de daha önceden bu yapıya göre yazılmış AbstractAction sınıfı mevcut yapmamız gereken o sınıfdan yeni sınıflar türetip kodumuzu aşağıdaki gibi değiştirmek.

class MesajGosterAction extends AbstractAction {
    public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(null, "Merhaba");
    }
}

class PencereKapatAction extends AbstractAction {
    private JFrame frame;    
    public void actionPerformed(ActionEvent e) {
        frame.dispose();
    }
    
    public void setFrame(JFrame frame) {
        this.frame = frame;
    }
}

class KayitEkleAction extends AbstractAction {
    private Connection con;
    private Statement stmt;

    public void actionPerformed(ActionEvent e) {
        try {
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            con = DriverManager.getConnection("jdbc:odbc:Bus", "", "");
            stmt = con.createStatement();
            stmt.executeUpdate("Insert Into Tablo (Deger) VALUES ('Calisti')");

            stmt.close();
            con.close();
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
    }
}

public class CommandFrame extends JFrame {
    private JButton buton;
    private AbstractAction komut;

    public CommandFrame(AbstractAction komut) {
        this.komut =komut;
        setTitle("Command Pattern");
        setSize(300, 200);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        initButton();
    }

    private void initButton() {
        buton = new JButton("Calistir");
        buton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                komut.actionPerformed(e);
            }
        });
        getContentPane().add(buton);
    }

    public static void main(String[] args) {
        CommandFrame frame = new CommandFrame(new MesajGosterAction());
        frame.setVisible(true);
    }
}

Gördüğünüz gibi CommandFrame sınıfı artık JDBC kodlarından,Kapatma kodlarından ve gelebilecek herhangi bir istekten haberi olmayacak yani onlardan bağımsız. İleride başka bir şeyi çalıştırma,başka işlem yapma isteği gelse bile CommandFrame sınıfının içini değiştirmeyeceğiz.Sadece AbstractAction sınıfından türeyen yeni komut sınıfları oluşturmak olacak.Ayrıca if-else kontrollerinden de kurtulduk. Bu bize kodun okunulabilirliği açısından da büyük kazanç sağladı. Aklınızın bir köşesinde bulunsun eğer çok if-else yazıyorsanız tasarımınızda ve kodunuzda bir problem olduğunun göstergesidir.

Bir diğer kullanım örneği olarak Java’daki Thread yapısını vereceğim.Çok basit bir kod üzerinde konumuzu inceleyelim

class EkranaYaz implements Runnable{
    public void run() {
        System.out.println("Ekrana Yazıyorum...");
    }
}

class TaklaAt implements Runnable{
    public void run() {
        System.out.println("Takla atıyorum...");
    }
}

public class ThreadOrnek {
    public static void main(String[] args) {
        Thread thread =new Thread(new EkranaYaz());
        thread.start();
        
        Thread thread1 =new Thread(new TaklaAt());
        thread1.start();
    }
}


Main metoduna bakacak olursanız iki tane Thread oluşturduk birinde ekrana yazıyoruz diğerinde takla atıyoruz:) Tabi gerçek hayatta muhtemelen sadece ekrana yazmakla kalmayıp büyük ihtimalle işi halletmek için taklalar atacaksınız.Fakat koda dikkat edecek olursanız Thread sınıfının içinde hiçbir değişiklik yapmadık(Java’da yapamayız zaten) .Sadece oluştururken yapıcısına Runnable interface’ini gerçekleyen iki adet sınıf verdik. Burada Command Pattern işlevini Runnable interface’i görüyor.

public interface Runnable {
    public abstract void run();
}


Runnable interface’ini uygulayan sınıflar tek bir metodun içini doldurmak zorunda run metodu. Ama o metodun içinde ister ekrana yazı yazdıralım ister veritabanına bağlanalım Thread sınıfını hiçbir şekilde ilgilendirmiyor.Bu şekilde Thread sınıfı yaptığı işlem kodundan bağımsız yeniden kullanılabilir bir sınıf haline gelmiş.Gördüğünüz gibi bu şekilde metod çağırımı bir interface arkasında soyutlanarak Command Pattern uygulanıyor.

Genelde Command Pattern örneklerini inceleyecek olursanız genel yapının şu şekilde olduğunu görürsünüz. Bir adet çalıştırılacak komutu simgeleyen Command interface’i bulunur. Örnek olarak Action, IExecutable,Startable,Runnable … gibi. Ayrıca bu interface’lerde genelde bir adet execute(),run(),start()…. gibi metod bulunur. Bu interface’i uygulayan sınıflar o metodu kendileri içinde doldururlar.

Gördüğünüz gibi kullanımı gayet kolay ve pratik olan bu tasarım kalıbı sayesinde oldukça esnek bir yapıda kod yazabildik. Vakit bulabilirsem gerçek bir projede kullanılan bir örneği de eklemeye çalışacağım. Design Patterns serüvenimiz daha yeni başlıyor sayılır diğer Patternlar ile çok yakında sizlerleyiz. Bizi izlemeye devam edin :)