-- Tasarım Kalıpları ve Prensipleri

Açık Kapalı Prensibi – Open Closed Principle (OCP)

Yazılımda değişmeyen tek şey değişimin kendisidir. Bunun için ilerde yapacağımız değişiklikleri göz önünde bulundurarak şuan yazdığımız kodları bu değişimleri sancısız atlatacak şekilde yazmalıyız. OCP prensibi de zaten bunun için var. OCP’ye göre yazılımda bir modül geliştirmeye açık fakat değiştirmeye kapalı olmalıdır. Yazılımımıza yeni bir özellik eklemek istediğimizde bunu mevcut kodlara, sınıflara dokunmadan yapabiliyor olmamız gerekir. Örneğin bir alışverişi sitesi tasarlıyoruz ve müşterilerin ürünleri filtreleyebilmesini istiyoruz. Bunu en basit halde aşağıdaki gibi Urun ve Filtre sınıflarıyla kodlayabiliriz.

using System.Collections.Generic;
 
namespace DesignPatternsTutorial
{
    public enum Renk
    {
        Yesil, Mavi, Sari
    }
 
    public enum Boyut
    {
        Buyuk, Kucuk, Orta
    }
 
    public class Urun
    {
        public string urunAdi;
        public Renk renk;
        public Boyut boyut;
      
        public Urun(string urunAdi, Renk renk,
 Boyut boyut)
        {
            this.urunAdi = urunAdi;
            this.renk = renk;
            this.boyut = boyut;
        }
    }
 
    public class Filtre
    {
        public IEnumerable<Urun> BoyutaGoreFiltrele(IEnumerable<Urun> urunler, Boyut boyut)
        {
            foreach (Urun u in urunler)
            {
                if (u.boyut == boyut)
                {
                    yield return u;
                }
            }
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            var urun1 = new Urun("Elma"Renk.Yesil, Boyut.Kucuk);
            var urun2 = new Urun("Kanepe"Renk.Mavi, Boyut.Orta);
            var urun3 = new Urun("Ağaç"Renk.Yesil, Boyut.Buyuk);
 
            Urun[] urunler = { urun1, urun2, urun3 };
 
            var filtre = new Filtre();
 
            filtre.BoyutaGoreFiltrele(urunler, Boyut.Buyuk);
        }
    }
 
}

Fakat buradaki eksiklerden biri ürünlerimizi sadece boyutuna göre filtreleyebiliyoruz, peki ya müşteri renge göre hatta hem renk hem boyuta göre filtrelemek istiyorsa, bunlar için ayrı ayrı metotlar yazmamız gerekiyor. Bu fonksiyonları müşteriye sağlamak için Filtre sınıfını değiştirerek aşağıdaki gibi kodlayalım.

public class Filtre
{
    public IEnumerable<Urun> BoyutaGoreFiltrele(IEnumerable<Urun> urunler, Boyut boyut)
    {
        foreach (Urun u in urunler)
        {
            if (u.boyut == boyut)
            {
                yield return u;
            }
        }
    }
 
    public IEnumerable<Urun> RengeGoreFiltrele(IEnumerable<Urun> urunler, Renk renk)
    {
        foreach (Urun u in urunler)
        {
            if (u.renk == renk)
            {
                yield return u;
            }
        }
    }
 
    public IEnumerable<Urun> RenkveBoyutaGoreFiltrele(IEnumerable<Urun> urunler, Boyut boyut, Renk renk)
    {
        foreach (Urun u in urunler)
        {
            if (u.boyut == boyut && u.renk == renk)
            {
                yield return u;
            }
        }
    }
}

Böyle yaparak mevcut Filtre sınıfını değiştirdik ve OCP’ye aykırı davrandık. Bu fonksiyonları mevcut sınıfı değiştirmeden Interfaceler aracılığıyla yapabiliriz. Burada aslında Sözleşme(Specification) tasarım kalıbını kullanacağız. 

namespace DesignPatternsTutorial
{
    public interface IFiltre<T>
    {
         IEnumerable<T> Filtrele(IEnumerable<T> urunler, ISozlesme<T> sozlesme);
    }
 
    public interface ISozlesme<T>
    {
        bool SartlarUygunMu(T t);
    }
}

ISozlesme‘yi kullanarak şartlarımıza uygun bir sözleşme hazırlayacağız(mavi renk, orta boyut gibi) ve IFiltre ile bu sözleşmeye uyan ürünlerimizi belirleyeceğiz. Bu arayüzleri aşağıdaki gibi kodlayarak renk ve boyut sözleşmesi hazırlayabiliriz.

public class YeniFiltre : IFiltre<Urun>
 {
     public IEnumerable<Urun> Filtrele(IEnumerable<Urun> urunler, ISozlesme<Urun> sozlesme)
     {
       foreach(Urun urun in urunler)
         {
             if (sozlesme.SartlarUygunMu(urun))
             {
                 yield return urun;
             }
         }
     }
 }
 
 public class RenkSozlesmesi : ISozlesme<Urun>
 {
     private  Renk renk;
 
     public RenkSozlesmesi(Renk renk)
     {
         this.renk = renk;
     }
 
     public bool SartlarUygunMu(Urun urun)
     {
         return urun.renk == renk;
     }
 }
 
 public class BoyutSozlesmesi : ISozlesme<Urun>
 {
     private Boyut boyut;
     public BoyutSozlesmesi(Boyut boyut)
     {
         this.boyut = boyut;
     }
     public bool SartlarUygunMu(Urun urun)
     {
         return urun.boyut == boyut;
     }
 }

İlk başta programımızda sadece RenkSozlesmesi adlı sınıfın var olduğunu düşünelim. Boyuta göre filtreleme yapmak için bu sınıfa ve YeniFiltre adlı sınıfa hiç dokunmadan BoyutSozlesmesi adlı bir sınıf oluşturduk ve bu sayede artık ürünlerimizi boyuta göre filtreleyebiliyoruz.

class Program
{
    static void Main(string[] args)
    {
        var urun1 = new Urun("Elma"Renk.Yesil, Boyut.Kucuk);
        var urun2 = new Urun("Kanepe"Renk.Mavi, Boyut.Orta);
        var urun3 = new Urun("Ağaç"Renk.Yesil, Boyut.Buyuk);
 
        Urun[] urunler = { urun1, urun2, urun3 };
 
        var filtre = new YeniFiltre();
 
        filtre.Filtrele(urunler, new RenkSozlesmesi(Renk.Mavi)); // Renge göre filtre
        filtre.Filtrele(urunler, new BoyutSozlesmesi(Boyut.Orta)); // Boyuta göre filtre
    }
}

Peki hem boyuta hem de renge göre filtreleme yapmak istediğimizde ne yapmamız gerekir? Bu sorunu aşağıdaki gibi bir BirlestiriciSozlesme sınıfını kullanarak aşabiliriz ve eski sınıflara dokunmadan OCP’ye uygun kod yazmış oluruz.

public class BirlestiriciSozlesme<T> : ISozlesme<T>
{
    private  ISozlesme<T> biriniciSozlesme, ikinciSozlesme;
 
    public BirlestiriciSozlesme(ISozlesme<T> biriniciSozlesme, ISozlesme<T> ikinciSozlesme)
    {
        this.biriniciSozlesme = biriniciSozlesme ?? throw new ArgumentNullException(nameof(biriniciSozlesme));
        this.ikinciSozlesme = ikinciSozlesme ?? throw new ArgumentNullException(nameof(ikinciSozlesme));
    }
 
    public bool SartlarUygunMu(T t)
    {
        return biriniciSozlesme.SartlarUygunMu(t) && ikinciSozlesme.SartlarUygunMu(t);
    }
}

Görüldüğü gibi BirlestiriciSozlesme sınıfı kendisine geçilen iki ayrı sözleşmeye uyulup uyulmadığına bakıyor, böylece hem renge hem boyuta göre filtreleme yapabiliyoruz.

class Program
{
    static void Main(string[] args)
    {
        var urun1 = new Urun("Elma"Renk.Yesil, Boyut.Kucuk);
        var urun2 = new Urun("Kanepe"Renk.Mavi, Boyut.Orta);
        var urun3 = new Urun("Ağaç"Renk.Yesil, Boyut.Buyuk);
 
        Urun[] urunler = { urun1, urun2, urun3 };
 
        var filtre = new YeniFiltre();
 
        var birlestiriciSozlesme = new BirlestiriciSozlesme<Urun>(new RenkSozlesmesi(Renk.Mavi), new BoyutSozlesmesi(Boyut.Orta));
 
        filtre.Filtrele(urunler, birlestiriciSozlesme); // renk ve boyuta göre filtreleme
    }
}
Bunu paylaş :

Yorum bırak

Yorum