Search

SOLID Prensipleri



0 yorum

SOLID Prensipleri

software development is not a jenga gameKod yazarken bir çok kişi herhangi bir kod yazım tekniğine bağlı kalmadan yazar. Bu durum ilerleyen durumlarda yazılımın maliyetinin artmasına yol açar, öyle ki bazı büyük senaryolarda ürün ortaya çıkmayabilir bile.
Yazılımımızı kötü hale getiren durumlar:
  • Esnemezlik (Rigidity): Kullanılan tasarımın geliştirilememesi ve ekleme yapılamaması. 
Bu duruma örnek olarak yazmış olduğumuz bir uygulamaya yeni bir modülün eklenmesi istendi. Eğer bu değişikliği gerçekleştirmek için proje üzerindeki bir çok sınıfta değişiklik yapmamız gerekiyorsa yada projemiz bu modülün eklenmesini gerçekleştirebilecek düzeyde değilse esnemez bir yazılım yapmışız demektir.
  • Kırılganlık (Fragility): Bir yerde yapılan değişikliğin başka bir yerde sorun çıkartması.
Projemiz üzerinde bir değişiklik yaptığımızda, başka yerlerdeki işleyiş bozuluyorsa kırılgan bir yazılıma sahibiz demektir.
  • Sabitlik (Immobility): Geliştirilmiş modülün başka yerde tekrar kullanılabilir (reusable) olmaması
Geliştirmiş olduğumuz modülü başka arayüzde yada katmanda kullanamıyorsak sabit bir yazılıma sahibiz demektir. Bu durum bize program geliştirme maliyetini oldukça attırır. Sürekli olarak kod tekrarına düşmek zorunda kalırız.

Bu durumlar "kötü kod" yazdığımızın göstergesidir. Kodlama yapılırken bu durumlara dikkat edilmelidir. Bu durumların önüne geçmek için SOLID prensiplerine bağlı kalmamız gereklidir.
Şimdi SOLID prensiplerini tanıyalım. Bu özellikleri genellikle senaryolar üzerinden anlatacağım.

(S)ingle Responsibility

single responsibility - erenguvercinSağ tarafta yer alan ünlü fotoğraf bu özelliği fazlasıyla anlatıyor. İsviçre çakısındaki her bir nesne tek bir göreve sahiptir ve diğer nesnelerle arasında herhangi bir ilişki bağı yoktur.

Bizim de geliştirmiş olduğumuz projelerde bu mantığa sahip olmalıyız. Örneğin temel olarak 3 katmanlı mimariye sahip bir proje yazdığımızı varsayalım. 
  • Data Access Layer
  • Business Layer
  • Presentation Layer
Burada her katman kendi işini yapmalıdır. Herhangi bir katman diğer katmanın gerçekleştirmesi gerektiği işlemleri kendi üzerinde yapmamalıdır. Örneğin bir ödeme sistemi modulu yazdığımızı düşünelim, burada Presenation Layer(Kullanıcı Arabirimi) üzerinden kullanıcı ödeme yap dediği zaman, ödeme yapılacak işlemini Business Layer katmanı yapmalıdır. Eğer ödeme yapma işlemini Presentation Layer üzerinde yapmaya çalışırsak Single Responsibility prensibinin dışına çıkmış oluruz. 
Single Responsibility - erenguvercin
Diğer bir örnek senaryo ise Business Layer üzerinde ViewState, Session gibi kodlamaların yapılmasıdır. Bu işlemler Web tabanlı projelerde gerçekleştirilen bir durumdur. Bu durumsa Presantation Layer'ın konusudur. Eğer Business Layer üzerinde bu işlemi gerçekleştirirsek, ileride Presantation Layer üzerine örneğin yeni bir mobil uygulaması arabirimi inşa etmek istediğimizde, bu uygulama Business Layer üzerindeki session işlemlerini tanıyamayacaktır. Bunun önüne geçmek için Single Responsibility prensibine bağlı kalmamız gereklidir.

(O)pen Closed Principle

solid erenguvercinBu prensip kısaca yeniliğe açık, modifikasyona(değişikliğe) kapalı bir kullanımı ifade eder. Sağ taraftaki klasikleşen resim bu durumu çok güzel ifade etmektedir. Bir ceket giymek içmek açık göğüs ameliyatına gerek yoktur değil mi? Ama biz bazen yazmış olduğumuz kötü kod yüzünden bu durumu gerçekleştirmek zorunda kalıyoruz.
Kodlamamızın mantığı daha sonradan gelecek isteklere açık olmalı. Yazmış olduğumuz module yeni bir özellik ekleneceği zaman bu özelliği kodun bir çok yerinde değişiklik yaparak eklememeliyiz. Tek bir noktada daha önce tanımlanmış özelliklere ek bir özellik gibi tanımlayarak diğer yerlerin bu özelliği tanımasını sağlamalıyız.
Örnek olarak raporlamamızın pdf ve word ortamında çıktı almasını istediğimizi varsayalım. Bu şekilde bir proje geliştirdikten sonra, excel ortamında da rapor alınmasını istediğimizi varsayalım. Bu durumu yazılım üzerinde tek bir noktada değişiklik yaparak sağlamalıyız.

(L)iskov Substitution Principle

Solid erenguvercin
Bu prensipse bize temelde nesnelerin sırf birbirine benzediği için birbirlerinden inherit edilmemesi gerektiğini anlatır. Resimde iki tane ördek görüyorsunuz, bu ördeklerin ikiside ötüyor ve birbirine benziyor diye birbirlerinden inherit etmemiz yanlış olur. Birisi canlı bir ördek olduğu için doğal olarak ötüyor, diğeriyse yapay yoldan örneğin bir batarya sayesinde ötüyor. Kısaca bu nesneler birbirinden temelde farklı nesnelerdir ve birbirinden inherit edilmesi ileride ciddi sıkıntılara yol açabilir.
Daha farklı ve kurumsal bir örnek verecek olursak, elektronik alet satan firma düşünelim. Bu firma iki temel ürün satsın masaüstü bilgisayar ve dizüstü bilgisayar. Bu iki ürünü birbirinden inherit ederse ne gibi sıkıntılar çıkar bakalım.
Urun:
  • Id
  • Ram
  • Anakart
  • Monitor
  • Kasa
Monitor özelliğine kadar herhangi bir sorun yok gibi, ancak monitor, kasa gibi özellikler masaüstü bilgisayara ait özellikler. Bu parçalar dizüstü bilgisayarda harici olarak bulunmuyor. Bu senaryo üzerinden gidilerek dizüstü bilgisayarlarda monitor ve kasa alanları girilmeden devam edilebilir. Ancak ilerleyen senaryolarda sorun çıkaracaktır. Bu durumları düşünerek kodlama yaparken Liskov Substitution Prinsibini aklımızdan çıkartmamalıyız.

(I)nterface Segregation Principle

Solid erenguvercinBu prensip için kısaca arayüzler(interface) birbirinden ayrıştırılmalıdır denilebilir. Yazmış olduğumuz bir interface implemente edilen classlar üzerinde hangi metotların çalışacağını söyleyecektir, ancak interface'in belirttiği metotların bazıları her class için geçerli olamayabilir. Bu gibi durumları öngörmek zor olduğu gibi kodlarımızın düzenli olması açısından bu kurala uyulmasına dikkat edilmelidir. Eğer bir interface çok sayıda metot içeriyor ve bazı classlar bu metotları karşılayamıyorsa interface'imizi parçalara ayırmayı düşünmeliyiz.
Bu durum için örnek bir senaryodan bahsedelim, bir fabrika düşünelim buradaki çalışanlar için bir interface olsun.
Interface içeriğinde yemekye() ve calis() adında iki metot bulundursun. Bu interface, erkekCalisan ve kadinCalisan sinifları için implemente edilerek çalıştırılabilir. Çünkü hem erkek hem kadın çalışan yemek yer ve çalışırlar. Ancak senaryomuza robotCalisan ekleyecek olursak, bu interface bizi ihtiyaç dışında bir metot yazmaya zorlar. robotCalisan yemekye() fonksiyonunu gerçekleştiremeyecektir. Prensibin mantığı şunu söyler sınıfın kullanmak zorunda olmadığı üyeler, metotlar kullanmak zorunda bırakılmamalıdır.
Bu durumun çözümü için farklı senaryolar gerçekleştirilebilir, örnek çözüm interface iki parçaya ayrılmalıdır. Öyle ki yemekye() fonksiyonu IYemekYe, calis() fonksiyonu ICalis olsun. erkekCalisan ve kadinCalisan için iki interface implemente edilir. Ancak robotCalisan için sadece ICalis interface'i implemente edilir. Böylece interface ayrıştırmasını çözmüş oluruz. 

Dependency Inversion Principle

DIP erenguvercinSon prensibimize geldik, bu prensip üst seviye modullerin,sınıfların alt seviye modullere,sınıflara bağımlı olmaması gerektiğini anlatır. Diğer bir ifadeyle bir nesneyi new ile üreterek kullanmak yerine nesneyi türediği veya implement ettiği ara bir sınıf aracılığıyla kullanmalıyız.

Klasik tasarım yöntemlerimizde üst seviye moduller, alt seviye modullere bağımlıdır. Bu durumun bize çıkaracağı sorunların başında alt seviye modullerde daha sık değişiklik meydana gelmesidir. Üst seviye moduller, alt seviye modullere bağımlı olurlarsa bu değişikliklerden etkilenir. Üst seviye modullerin etkilenmesi ise yazılımımızda küçük bir değişiklik yapılsa bile bütün yapının etkilenmesi sonucunu doğurur. 
Ayrıca bağımlılık tekrar kullanılabilirlik durumunu öldürür. Bunun önüne geçmek için modulleri birbirinden soyutlamamız gerekir.
Prensibimizin resminden de görüldüğü gibi, bir ampul değiştirmek için bütün tesisatı değiştirmemize gerek yok değil mi?
Aşağıda vermiş olduğum resmin sol tarafında olmaması gereken bağımlı durum şekli, sağ tarafta ise olması gereken şekil bulunuyor.
solid dip


Bu durumu bir örnek üzerinden inceleyelim. Bir elektronik mağazanın e-ticaret sitesini yaptığımızı düşünelim. Bu mağaza ürün üzerinde iki işlem gerçekleştirebiliyor, satış yada kiralama. Firma bizden dizüstü ve masaüstü bilgisayar ürünlerinin olduğunu söyledi ve kodlarımızı buna göre gerçekleştiriyoruz. Öncelikle üst seviye modülün alt seviye modüle bağımlı olduğu senaryoyu gerçekleştireceğiz.


    class Program
    {
        static void Main(string[] args)
        {
            Urun urun = new Urun() { 
                dizUstu = new DizUstu(),
                masaUstu = new MasaUstu()
            };
            //masaustu seçelim
            urun.UrunTipi = UrunTipi.MasaUstu;
            urun.SatisYap();
            urun.Kirala();
            //dizustu seçelim
            urun.UrunTipi = UrunTipi.DizUstu;
            urun.SatisYap();
            urun.Kirala();
            Console.ReadLine();
        }

        public enum UrunTipi 
        {
            DizUstu, MasaUstu
        }

        public class Urun 
        {
            public DizUstu dizUstu { get; set; }
            public MasaUstu masaUstu { get; set; }
            public UrunTipi UrunTipi { get; set; }

            public void SatisYap()
            {
                switch (UrunTipi)
                {
                    case UrunTipi.DizUstu:
                        dizUstu.Satis();
                        break;
                    case UrunTipi.MasaUstu:
                        masaUstu.Satis();
                        break;
                }
            }
            public void Kirala()
            {
                switch (UrunTipi)
                {
                    case UrunTipi.DizUstu:
                        dizUstu.Kirala();
                        break;
                    case UrunTipi.MasaUstu:
                        masaUstu.Kirala();
                        break;
                }
            }
        }

        public class DizUstu
        {
            public void Satis()
            {
                Console.WriteLine("Dizüstü satıldı");
            }
            public void Kirala()
            {
                Console.WriteLine("Dizüstü kiralandı");    
            }
        }

        public class MasaUstu
        {
            public void Satis()
            {
                Console.WriteLine("Masaüstü satıldı");
            }
            public void Kirala()
            {
                Console.WriteLine("Masaüstü kiralandı");
            }
        }

    }

Bu örnekte herşey düzgün bir şekilde çalışıyor. Ancak mağazanın bizden yeni bir ürün ekleyeceği yönünde bir talebi olduğunu düşünelim. Örneğin tablet bilgisayar ürünü satış ve kiralamasına da başladık dedi. Bu durumda kodumuzu şu şekilde güncellemek zorunda kalacağız.



    class Program
    {
      static void Main(string[] args)
        {
            Urun urun = new Urun() { 
                dizUstu = new DizUstu(),
                masaUstu = new MasaUstu(),
                tablet = new Tablet() //*YENİ TALEP
            };
            //masaustu seçelim
            urun.urunTipi = UrunTipi.MasaUstu;
            urun.SatisYap();
            urun.Kirala();
            //dizustu seçelim
            urun.urunTipi = UrunTipi.DizUstu;
            urun.SatisYap();
            urun.Kirala();
            //tablet seçelim
            urun.urunTipi = UrunTipi.Tablet; //*YENİ TALEP
            urun.SatisYap();
            urun.Kirala();
            Console.ReadLine();
        }

        public enum UrunTipi 
        {
            DizUstu, MasaUstu, Tablet //*YENİ TALEP
        }

        public class Urun 
        {
            public DizUstu dizUstu { get; set; }
            public MasaUstu masaUstu { get; set; }
            public UrunTipi urunTipi { get; set; }
            public Tablet tablet { get; set; } //*YENİ TALEP

            public void SatisYap()
            {
                switch (urunTipi)
                {
                    case UrunTipi.DizUstu:
                        dizUstu.Satis();
                        break;
                    case UrunTipi.MasaUstu:
                        masaUstu.Satis();
                        break;
                    case UrunTipi.Tablet: //*YENİ TALEP
                        tablet.Satis();
                        break;
                }
            }
            public void Kirala()
            {
                switch (urunTipi)
                {
                    case UrunTipi.DizUstu:
                        dizUstu.Kirala();
                        break;
                    case UrunTipi.MasaUstu:
                        masaUstu.Kirala();
                        break;
                    case UrunTipi.Tablet: //*YENİ TALEP
                        tablet.Kirala();
                        break;
                }
            }
        }

        public class DizUstu
        {
            public void Satis()
            {
                Console.WriteLine("Dizüstü satıldı");
            }
            public void Kirala()
            {
                Console.WriteLine("Dizüstü kiralandı");    
            }
        }

        public class MasaUstu
        {
            public void Satis()
            {
                Console.WriteLine("Masaüstü satıldı");
            }
            public void Kirala()
            {
                Console.WriteLine("Masaüstü kiralandı");
            }
        }

        public class Tablet //*YENİ TALEP
        {
            public void Satis()
            {
                Console.WriteLine("Tablet satıldı");
            }
            public void Kirala()
            {
                Console.WriteLine("Tablet kiralandı");
            }
        }
    }

Yapılan değişiklikler //*YENİ TALEP ifadesiyle gösterilmiştir. Dikkat ederseniz küçük bir istek dahilinde bile kodumuzun bir çok yerinde değişiklik yaptık. 
Burada Urun classımız üst modüldür ve alt modüllere bağımlıdır. Bu tip sorunların önüne geçmek için DIP prensibine bağlı olarak kodlama yapmamız gerekmektedir. 
Bu durum tersine bağımlılık standartına göre düzenlenir ve prensibimize bağlı kod yazarsak aşağıdaki şekilde düzenli ve bağımlılıktan kurtulmuş bir yapıya kavuşmuş olacağız. 
İlk önce tablet ürünü eklenmemiş haldeki kodu yazalım.


   class Program
    {
        //Client
        static void Main(string[] args)
        {
            //Artık ürüne interface yardımıyla ulaşıyoruz
            IUrun urunIDizUstu = new DizUstu();
            Urun urunDizUstu = new Urun(urunIDizUstu);
            urunDizUstu.Satis();
            urunDizUstu.Kirala();
            IUrun urunIMasaUstu = new MasaUstu();
            Urun urunMasaUstu = new Urun(urunIMasaUstu);
            urunMasaUstu.Satis();
            urunMasaUstu.Kirala();
            Console.ReadLine();
        }

        //Abstraction Modul
        public interface IUrun
        {
            void Satis();
            void Kirala();
        }

        //Üst Seviye Modül
        public class Urun
        {
            IUrun _urun;
            public Urun(IUrun urun)
            {
                _urun = urun;
            }

            public void Satis()
            {
                _urun.Satis();
            }
            public void Kirala()
            {
                _urun.Kirala();
            }
        }


        //Alt Seviye Modül
        public class DizUstu : IUrun
        {
            public void Satis()
            {
                Console.WriteLine("Dizüstü satıldı");
            }
            public void Kirala()
            {
                Console.WriteLine("Dizüstü kiralandı");
            }
        }

        //Alt Seviye Modül
        public class MasaUstu : IUrun
        {
            public void Satis()
            {
                Console.WriteLine("Masaüstü satıldı");
            }
            public void Kirala()
            {
                Console.WriteLine("Masaüstü kiralandı");
            }
        }
    }

Kodlamaya dikkat edecek olursanız üst seviye modülün alt seviye modüle bağımlılığı yok, tamamen kendi içerisinde çalışıyor. Şimdi yeni ürün olan tableti ekleyerek kodu yazalım. Dikkat edin üst seviye modülde hiçbir değişiklik yapılmayacak.


 
class Program
    {
        //Client
        static void Main(string[] args)
        {
            //Artık ürüne interface yardımıyla ulaşıyoruz
            IUrun urunIDizUstu = new DizUstu();
            Urun urunDizUstu = new Urun(urunIDizUstu);
            urunDizUstu.Satis();
            urunDizUstu.Kirala();
            IUrun urunIMasaUstu = new MasaUstu();
            Urun urunMasaUstu = new Urun(urunIMasaUstu);
            urunMasaUstu.Satis();
            urunMasaUstu.Kirala();

            IUrun urunITablet = new Tablet();//*YENİ TALEP
            Urun urunTablet = new Urun(urunITablet);
            urunTablet.Satis();
            urunTablet.Kirala();
            Console.ReadLine();
        }

        //Abstraction Modul
        public interface IUrun
        {
            void Satis();
            void Kirala();
        }

        //Üst Seviye Modül
        public class Urun
        {
            IUrun _urun;
            public Urun(IUrun urun)
            {
                _urun = urun;
            }

            public void Satis()
            {
                _urun.Satis();
            }
            public void Kirala()
            {
                _urun.Kirala();
            }
        }


        //Alt Seviye Modül
        public class DizUstu : IUrun
        {
            public void Satis()
            {
                Console.WriteLine("Dizüstü satıldı");
            }
            public void Kirala()
            {
                Console.WriteLine("Dizüstü kiralandı");
            }
        }

        //Alt Seviye Modül
        public class MasaUstu : IUrun
        {
            public void Satis()
            {
                Console.WriteLine("Masaüstü satıldı");
            }
            public void Kirala()
            {
                Console.WriteLine("Masaüstü kiralandı");
            }
        }

        //Alt Seviye Modül *YENİ TALEP
        public class Tablet : IUrun
        {
            public void Satis()
            {
                Console.WriteLine("Tablet satıldı");
            }
            public void Kirala()
            {
                Console.WriteLine("Tablet kiralandı");
            }
        }
    }

Dikkat edecek olursanız sadece yeni bir alt seviye tablet classı ekledim ve client tarafında bu classın metotlarını çalıştırdım. Üst seviye modülüm içerisinde yeni taleple ilgili hiç bir değişiklik yapmadan işlemimi gerçekleştirmiş oldum. Bu yöntem bize esneklik, yeniden eklenebilirlik, geliştirebilirlik gibi bir çok yazılım özelliklerini kazandırıyor.
Yararlı olması dileğiyle... 

0 yorum:

Yorum Gönder

Check Page Rank
DMCA.com