Previous
Next

Design Patterns - Tasarım Desenleri

by Cem Kefeli 26. Eylül 2017 00:43

Aslında;

Ben şu işi böyle yapıyorum. Bu şekilde çözüm buldum, çok da kullanışlı, işleri çok da kolaylaştırıyor. Sen de kullan diye de standard bir diagram haline getirdim. Al işte bu da o diagram!"

demektir 'Design Pattern' ya da Türkçe olarak 'Tasarım Deseni'. Bu kadar da basittir felsefe olarak...

Design Patters - Tasarım DesenleriBirilerine bir yön vermeyi, sık yaşanan sorunları ortak kalıplarda en ideal yöntem ile çözmeyi amaçlar. Çözmekten de ileri gider ve herkesin bir çırpıda anlayabileceği, ulaşabileceği ve uygulayabileceği sadelikte ve standart söylemle dile getirir söylemek istediklerini. 'Best Practice' ortaya koyar bir başka söylemle. Peki en iyi olduğunu kim nasıl tescil eder bu yöntemin? Aslında hiç kimse. Zamana ve tercihe bağlı bir algı bu. Kabul gören tasarım zamanla yayılır ve doğal yollardan tescil edilmiş olur. Yani konu bir probleme getirilen en iyi çözüm iddiasını taşımak değildir aslında.

Yazılım diline özgü de değildir. Bir tasarım deseni farklı farklı yazılım dilleri kullanılarak uygulanabilir. Desen, yazılımın nasıl olması gerektiğini ortaya koyar ve resmi çizdikten sonra kenara çekilir, gerisini geliştiriciye bırakır. Geliştiriciye de kolaylıklar sağlar. Neyi nasıl yapması gerektiğini daha önce aşina olduğu tasarım desenleri yardımıyla kolayca belirleyebilir. Ya da kendisine ait olmayan bir kodu okurken tasarım deseni sayesinde kaynak koda kolayca adapte olabilir. Kendisine ait olmayan bir kaynak kod üzerinde geliştirmeye başladığında tekrara düşmeden, yapıyı bozmadan yazılım geliştirebilir.

Peki neden ihtiyaç var desenlere? Bu soruyu sormadan önce belki de bir yazılımın ne kadar kaliteli ve güncele uygun olduğunun nasıl anlaşılabileceğini düşünmek gerekir. Anlayabilmek için genişletilmiş hali yedi adet ama temelde dört adet başlık vardır vardır litaratürde bahsedilen ve incelenmesi gereken;

  • Rigidity: Bir yazılıma yeni bir özellik eklenmesi, gelen yeni bir iş isteğinin karşılanması için yazılımın birçok yerinin değiştirilmesi gerekiyorsa ve bu değişiklikler de çok köklü yerlere dokunuyorsa 'değişmezlik' kavramı için sıkıntı var demektir. Normal şartlarda yalnızca yeni özellikle ilgili yerlere dokunmak yeterli iken bir de bu yeni özellik haricinde birçok farklı noktaya dokunmak gerekiyorsa yazılım artık ağır yavaş ölüyor demektir.
  • Fragility: Üzerinde geliştirme yapılmak istenen yazılım değişiklik isteklerine çok karmaşık cevap veriyorsa örneğin asıl iş ile mantıksal bağı olmayan birçok yeri etkiliyorsa ve bu etkiler yıkıcı olabilecek türden ise yazılım için ciddi anlamda kırılganlık var demektir.
  • Immobility: Günümzde artık birçok yazılım, genel amaçlar için yazılmış kütüphaneleri kullanarak iş görebiliyor. Yani her kütüphane kendi işinde özelleşmiş özellikleri barındırıyor ve yazılımlar ise bu kütüphanelerin yalnızca birer müşterisi olarak konumlanıyor. Eğer yazılım bu mantığa uygun bir şekilde geliştirilmemişse yani bir değişiklik modüler bir şekilde yapılamıyorsa ve birden çok yerde aynı kodlamanın uygulanmasını mecbur kılıyorsa o yazılım için taşınabilirlik anlamında sıkıntılar var demektir.
  • Viscocity: Bir yazılım üzerinde değişiklik yapılması düşünüldüğünde olması gerekeni yapmak yani design temelli çalışmak taba tabirle uyduruk yöntemle çalışmaktan daha zor ise o zaman yazılım artık güncel şartlara da ayak uyduramıyor demektir. Bu konu temelde yazılım ve çevre birim olmak üzere iki alt konu olarak inceleniyor litaratürde. Hani derler ya "Bu konu üzerime yapıştı kaldı. Kurtulamıyorum!" diye. Burada da durum aynıdır. Belki yazılımın artık günceli reddetmesi, ayak uyduramaması nedeniyle üzerinize yapışıp kalmıştır, belki de artık yazılımın yaşadığı ortam günceli takip edemiyordur, derleme zamanı çok uzamıştır bu nedenle üzerinize yapışıp kalmıştır.
Bu dört konuyu inceledikten sonra aslında fark edilmesi gereken en önemli çıkarım 'Acaba ben bu dört konuya sürüklenmemek ve bu olumsuzlukları yaşamamak adına neler yapmalıyım?' olmalıdır. İşte bu dört konuyu bertaraf edebilmek ve hayatımızdan uzaklaştırmak adına baş harflerinin bir araya gelmesiyle oluşturulan ve 'SOLID' olarak anılan bir dizi prensipler silsilesi bulunmakta.
  • Single Responsibility Principle (SRP): Der ki, bir sınıfın değiştirilmesi için birden fazla neden olmamalıdır. Yani açık söylemle, bir sınıf yalnızca ama yalnızca bir işe odaklanarak tasarlanmalıdır. Bu, bir sınıfın içinde yalnızca tek bir iş yapılacak anlamına gelmiyor, tabiki onlarca farklı işi yapan methotlar bulunabilir ama bu işlerin tümü aynı konu ile ilgili olmalıdır. İşine odaklan der yani SRP.

    Somut bir örnek vermek gerekirse; Eğer müşterilerle ilgili işler için 'Musteriler' isimli bir sınıf yazılmışsa, bu sınıfın içine dosyaya yazma/okuma işlemlerini ya da veritabanı yazma/okuma işlerini de katmamak gerekir. Bırakalım 'Musteriler' yalnızca müşteri bilgileri ile ilgili lojiği yönetsin. Mesela müşteri puanını yaşına göre göre hesaplasın. Mesela müşteriye özel teklifleri oluştursun. Eğer veritabanı işlerini de yaparsa iki gün sonra veritabanı teknolojisi değişirse 'Musteriler' türünde tüm sınıflarda da değişiklik yapmak gerekir ki bu çok hoş bir durum olmaz.
  • Open Closed Principle (OCP): Der ki, bir sınıf üzerinde genişletme yapılabilir ama modifikasyondan kaçınılmalıdır. Yani bir sınıf için yeni bir isteği karşılayacaksan var olan yapıyı değiştirme bırak dursun ama yeni özellik ile ilgili yeni bir method uygulayabilirsin. Ya da farklı bir interface oluşturarak yeni özelliğin bu interface üzerinden kalıtlanmasını sağlayabilirsin. Böylece etki minimum seviyede, işin takibi ise maksimum seviyede olur. Bu düşünceye göre mevcut sınıf içerisindeki değişiklikler ancak ve ancak hata düzeltme ile ilgili olmalıdır.
  • Liskov Substitution Principle (LSP): Der ki bir sınıftan (üst sınıf) türeyen tüm sınıflara (alt sınıf) ait nesneler, günün birinde üst sınıf ile yer değiştirdiğinde aynı davranışı sergilemelidir. Yani bir başka söylemle alt sınıflar çok detaylı düşünülerek hazırlanmalıdırlar. Günün birinde bir alt sınıfın üst sınıfa ait bir özelliği taşımadığı/taşıyamadığı ortaya çıkmamalıdır. Daha ilk andan itibaren bu durum dikkate alınmalıdır.

    Somut bir örnek vermek gerekirse; 'Kus' isminde bir sınıftan 'Karga', 'Martı' ve 'Devekusu' isimli üç sınıf türettiğimizi düşünelim. Ana sınfta yer alan 'kanatUzunluguHesapla' ve 'ucmayaBasla' metodlarını inceleyelim. Her üç kuşun da kanatları olduğu için burada sıkıntı yoktur, kanat uzunluğu hesaplanabilir. Ama 'Devekusu' uçamadığı için 'ucmayaBasla' metodu Devekuşu Kaberenin muhteşemleri Metin Akpınar, Zeki alasya ve tüm ekibin söylediği gibi bu sınıf için anlamsız kalacaktır ve LSP çiğnenmiş olacaktır. Sınıf içerisinde her ne kadar anlam bütünlüğü var gibi görülse de aslında olması gereken 'ucmayaBasla' metodunun örneğin 'IUcabilen' isimli bir arabirim (interface) içersinde yer alması ve uçabilen kuş türlerinin bu arabirim üzerinden genişletilmesidir. Ne de olsa devekuşunun kanadı var ama yerde yani uçamıyor, hörgücü yok ama bir yandan da adı deve Wink
  • The Interface Segregation Principle (ISP): Der ki bir arabirim içerisinde işine neler lazımsa yalnızca onlar bulunsun. Yani bu arabirimden bir sınıf genişlettiğin zaman genişletilen sınıfın amacı dışında metodları uygulamak zorunda kalmamalısın. Her bir özelleşmiş mantılsak iş grubu için ayrı arabirim yaz. Böylece hem daha derli toplu ve okunabilir bir yapıya sahip olursun hem de arabirimden genişletilen sınıflar da yalnızca genişletme amacı ile ilgili metodları barındırmış olurlar.
  • Dependency Inversion Principle (DIP): Der ki olabildiğince arabirim ve soyutlama kullanmaktan çekinme. Bu arabirimleri ve soyutlamaları da özellikle üst sınıfların alt sınıflara bağlılığını ortadan kaldırmak için kullan. Üst sınıfların alt sınıflara bağlımlılığı yerine alt sınıfların üst sınıflara bağımlı olduğu yapılar oluştur.

    Somut bir örnek vermek gerekirse; Elimizde 'KuslariUcur' isimli bir sınıf olsun. Bu sınıf içerisinde de 'KuslariSagaUcur', 'KuslariSolaUcur' isminde iki adet de sınıf örneği bulundursun. 'KuslariUcur' sınıfı içerisindeki 'UcurmayaBasla' isimli methot ise bir yön bilgisini girdi olarak kabul ederek bu yön bilgisine göre ya kuşları sağa uçursun ya da sola uçursun. Bu durumda kod parçacığımız aşağıdaki gibi olur;

    DIP yaklaşımı olmayan bir kod parçacığı  |  Gizle  |  Göster
    public class KuslariUcur {
            private Kus kus;
            private KuslariSagaUcur kuslariSagaUcur =new KuslariSagaUcur();
            private KuslariSolaUcur kuslariSolaUcur =new KuslariSolaUcur();
     
            public KuslariUcur(Kus kus){ 
            		this.kus=kus;
            }
     
            public void UcurmayaBasla(int yon)
            {
                    if(yon==0)
                            kuslariSagaUcur.ucur(kus);
                    else if(yon==1)
                            kuslariSolaUcur.ucur(kus);
            }
    }
    
    public class Kus {
            private string tur;
            private int kanatUzunlugu;
    }
    
    public class KuslariSagaUcur {
           public void ucur(Kus kus){
                ...
          }
    }
     
    public class KuslariSolaUcur {
           public void ucur(Kus kus){
                ...
          }
    }
    

    Eğer dikkat ederseniz yarın bir gün kuşları eğer ileriye doğru da uçurmak istersek yeni bir sınıf yazmak durumunda kalırız. Burada sıkıntı yok. Her durumda zaten yeni sınıf yazmak gerekir. Ama asıl sıkıntı bu sınıfları kullanan sınıf olan 'KuslariUcur' icerisinde de değişiklik yapılması gerektiği. Buraya bir çözüm bulunmalı! O çözüm ise kuşları uçuran sınıfları bir arayüz üzerinden uygulamak ve 'KuslariUcur' içerisinde bu arayüzü kullanmak. O da aşağıdaki gibi;

    DIP yaklaşımı olmayan bir kod parçacığı  |  Gizle  |  Göster
    public class KuslariUcur {
        private Kus kus;
        private IKuslarUcsun iKuslarUcsun;
    
        public KuslariUcur(Kus kus) {
            this.kus = kus;
        }
    
        public void UcurmayaBasla() {
            iKuslarUcsun.ucur(kus);
        }
    }
    
    interface IKuslarUcsun {
        void ucur(Kus kus);
    }
    
    class Kus {
        private String tur;
        private int kanatUzunlugu;
    }
    
    class KuslariSagaUcur implements IKuslarUcsun {
        public void ucur(Kus kus) {
            ...
        }
    }
    
    class KuslariSolaUcur implements IKuslarUcsun {
        public void ucur(Kus kus) {
            ...
        }
    
    }
    
    class KuslariİleriUcur implements IKuslarUcsun {
        public void ucur(Kus kus) {
            ...
        }
    }
    
    
Ne kadar da güzel değil mi? Artık onuncu satırda gösterildiği gibi arayüz üzerinden yapılan method çağımı her işimizi hallediyor. Üstüne üstlük yarın birgün kuşları geriye uçurmak istersek tek yapmamız gereken yeni bir 'KuslariGeriyeUcur' isimli sınıf yazıvermek. Üst sınıfta hiçbirşey yapmamıza gerek yok.
 
Unutmadan, yazının sonuna gelince fark etmiş olmalısınız ki aslında bu yazı içerisinde bahsedilenlerden bazıları zaten sizin günlük SDLC hayatınızda uygulamış olduğunuz ve dikkatinizde olan konular. Zaten desenlerin uygulanabilirliği de tam bu noktada ortaya çıkıyor. Her biri somut ve uygulanabilir nitelikte konular.
 

Yorum ekle

biuquote
  • Yorum
  • Canlı önizleme
Loading

Hakkımda...

Cem KEFELİ

Electronics and
Telecommunication Eng.
devamı...


Son yapılan yorumlar...

Comment RSS

Yasal bir uyarı...

Disclaimer"Bu web sitesinde görmüş olduğunuz bilgilerin, dokümanların ve diğer materyallerin kullanılmasından doğabilecek hiç bir sorumluluktan site sahibi sorumlu tutulamaz. Web sitesi içerisinde yer alan yazılar, yorumlar, resimler ve diğer tüm içerikler yalnızca sahibinin görüşünü yansıtmakta olup içeriğin sahibi kişilerin çalıştığı kurumları bağlayıcı hiç bir nitelik taşımamaktadır. Yapılan tüm alıntılar mutlaka kaynak gösterilerek verilmeye çalışılmaktadır. Web sitesi içerisinde bulunan ilgili materyaller, ilgili yasal kurumlar tarafından uygun görülmemesi durumda kaldırılacaktır."
General