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

Bu serinin ilk makalesi olan yazılım tasarım prensipleri (SOLID) içerisinde kısa tanım olarak bahsettiğim açık – kapalı prensibini (open – closed principle) örneklendirerek açıklamak istiyorum.

Yukarıdaki görselde müşterilerin genel davranışlarından biri olan projeden sürekli talepte bulunma isteği resmedilmiş. Müşteri açısından bakıldığında birçok talep yapıyor olmak gayet olağan bir davranıştır. Çünkü gerçek hayatta bir fabrika veya başka bir kurum müşteriniz olabilir ve bu kurumlar da insanlar gibi yaşayan birer organizma gibidir. İş yapış şekli, yöntemler, ürünler, tasarımlar ve daha sayamadığım bu gibi konular zaman içinde sürekli değişime uğrar. Yazılımın da bu talepleri hızlı ve esnek bir biçimde karşılıyor olması beklenir.

Bir uygulama senaryosu üzerinden devam edelim. Örneğimizde çeşitli ürünler üreten bir fabrikamız olduğunu düşünelim. Bu fabrikanın piyasa ve müşteri ihtiyaçlarını karşılamak amacı ile her ay onlarca yeni tip ürünü piyasaya sürdüğünü düşünelim. Şimdi senaryoyu açık – kapalı prensibine (open – closed principle) uymayan şekilde kodlayalım ve programın ilerleyen zamanlarda yaşayacağı problemleri görelim.

İlk aşamada fabrikada üretilen ürünlerin tanımlanması ile başlayalım. Aşağıdaki kodda 2 adet ürün tanımlandı fakat buradaki ürünlerin sayısı istenildiği kadar çoğaltılabilir.

namespace UretimSenaryosu
{
    public class Urun1
    {
        public void Uret()
        {
            //Burada ürün 1'e özel üretim metotları çalıştırılıyor.
            Console.WriteLine("Ürün 1'e özel üretim yapıldı.");
        }
    }

    public class Urun2
    {
        public void Uret()
        {
            //Burada ürün 2'ye özel üretim metotları çalıştırılıyor.
            Console.WriteLine("Ürün 2'ye özel üretim yapıldı.");
        }
    }
}

Yukarıda görülen “Uret” metodu sembolik bir metottur. Bu metot içerisinde sadece o ürüne özel bazı akışlar, o ürüne özel kurallar, varsa özel hesaplamalar ve/veya ürüne özel olarak şu an aklıma gelmeyen diğer her türlü işlevlerin bulunduğunu düşünebiliriz. Tabi ki bunlar sektöre göre değişim gösterecektir.

İkinci aşamada fabrikadaki ürünleri üreten Fabrika isimli bir sınıfımız olduğunu düşünelim ve bu sınıfı kodlayalım.

namespace UretimSenaryosu
{
    public class Fabrika
    {
        public void Urun1Uret(Urun1 urun)
        {
            //Ürün 1 üretiliyor.
            urun1.Uret();
        }

        public void Urun2Uret(Urun2 urun)
        {
            //Ürün 2 üretiliyor.
            urun2.Uret();
        }
    }
}

Son olarak da Program.cs içerisindeki kodları yazalım.

namespace UretimSenaryosu
{
    public class Uretim
    {
        public static void Main(string[] args)
        {
            Urun1 urun1 = new Urun1();
            Urun2 urun2 = new Urun2();

            Fabrika fabrikam = new Fabrika();

            fabrikam.Urun1Uret(urun1);
            fabrikam.Urun2Uret(urun2);
        }
    }
}

Senaryo 1 – Üretim Hattına Yeni Bir Ürün Eklendi.

Buraya kadar basit anlamda bir üretim senaryosu oluşturduk. Şimdi ise üretim hattımıza Ürün 3 isimli yeni bir ürün geldiğini düşünelim. Bu yeni ürüne göre programımız aşağıdaki gibi şekillenecek. İlk önce yeni ürüne ait sınıfımızı oluşturuyoruz.

namespace UretimSenaryosu
{
    public class Urun1
    {
        public void Uret()
        {
            //Burada ürün 1'e özel üretim metotları çalıştırılıyor.
            Console.WriteLine("Ürün 1'e özel üretim yapıldı.");
        }
    }

    public class Urun2
    {
        public void Uret()
        {
            //Burada ürün 2'ye özel üretim metotları çalıştırılıyor.
            Console.WriteLine("Ürün 2'ye özel üretim yapıldı.");
        }
    }

    public class Urun3
    {
        public void Uret()
        {
            //Burada ürün 3'e özel üretim metotları çalıştırılıyor.
            Console.WriteLine("Ürün 3'e özel üretim yapıldı.");
        }
    }
}

Sonrasında ise üretim işlemini yapan Fabrika sınıfımıza Ürün 3 için gerekli üretim kodlarını ekliyoruz.

namespace UretimSenaryosu
{
    public class Fabrika
    {
        public void Urun1Uret(Urun1 urun)
        {
            //Ürün 1 üretiliyor.
            urun1.Uret();
        }

        public void Urun2Uret(Urun2 urun)
        {
            //Ürün 2 üretiliyor.
            urun2.Uret();
        }

        public void Urun3Uret(Urun3 urun)
        {
            //Ürün 3 üretiliyor.
            urun3.Uret();
        }
    }
}

Son olarak da Program.cs içerisine Ürün 3 için üretim yapma komutunu veriyoruz.

namespace UretimSenaryosu
{
    public class Uretim
    {
        public static void Main(string[] args)
        {
            Urun1 urun1 = new Urun1();
            Urun2 urun2 = new Urun2();
            Urun3 urun3 = new Urun3();

            Fabrika fabrikam = new Fabrika();

            fabrikam.Urun1Uret(urun1);
            fabrikam.Urun2Uret(urun2);
            fabrikam.Urun3Uret(urun3);
        }
    }
}

Peki her ürün eklendiğinde Fabrika sınıfımızı bu şekilde değiştirerek her yeni ürün için bir üretim yapma kodu mu gireceğiz. Elbette işler bu şekilde de yürüyebilir fakat bu açık – kapalı prensibine (open – closed principle) oldukça ters bir mimari oluşturmaktadır. Her seferinde tüm kodların elden geçmesi belli bir zaman sonra programın bakımında zorluklar çıkaracak ve işler iyice karmaşık hale gelecektir. Oysa ki yeni bir ürün eklendiğinde sadece ilgili yeni ürüne ait sınıfımızı (model olarak da bilinir) eklesek ve üretim yapan Fabrika isimli sınıfa hiç dokunmasak nasıl olurdu?

Senaryo 2 – Aynı Kodun Açık – Kapalı Prensibine (Open – Closed Principle) Göre Yazılması

Açık – kapalı prensibine (open – closed principle) göre kodlama işlemi soyut sınıflar (abstract class) kullanılarak da yapılabilir fakat ben arayüz (interface) uygulamalarını daha çok sevdiğim için bu yöntemle yapacağım. İlk Aşama olarak tekrar iki adet ürün sınıfımızı yazalım. Bu sefer öncekinden farklı olarak bir üretim arayüzü yazacağız ve tüm sınıfların bu arayüzü uygulamasını sağlayacağız.

namespace UretimSenaryosu
{
    public interface IUrun
    {
        void Uret();
    }

    public class Urun1: IUrun
    {
        public void Uret()
        {
            //Burada ürün 1'e özel üretim metotları çalıştırılıyor.
            Console.WriteLine("Ürün 1'e özel üretim yapıldı.");
        }
    }

    public class Urun2: IUrun
    {
        public void Uret()
        {
            //Burada ürün 2'ye özel üretim metotları çalıştırılıyor.
            Console.WriteLine("Ürün 2'ye özel üretim yapıldı.");
        }
    }
}

Sınıflarımız bir arayüzü uygulayacak şekilde tanımladıktan sonra üretim işleminin yapıldığı Fabrika sınıfını da açık – kapalı prensibine (open – closed principle) göre yazalım. Farkedileceği üzere artık Fabrika sınıfının içerisinde sadece bir adet IUrun tipinde parametre kabul eden bir metodumuz var ve bu metot tüm ürünlerin üretimini yapabiliyor.

namespace UretimSenaryosu
{
    public class Fabrika
    {
        public void Uret(IUrun urun)
        {
            urun.Uret();
        }
    }
}

Son olarak da Program.cs içerisindeki kodları açık – kapalı prensibine (open – closed principle) göre düzenleyelim. Buradaki değişiklikte de görüleceği üzere üretilecek tüm ürünleri Fabrika sınıfı içerisindeki Uret metoduna gönderdik. Buradaki Uret metodu artık ürün bağımsız şekilde kendisine gönderilen her ürün için üretim yapabiliyor.

namespace UretimSenaryosu
{
    public class Uretim
    {
        public static void Main(string[] args)
        {
            Urun1 urun1 = new Urun1();
            Urun2 urun2 = new Urun2();

            Fabrika fabrikam = new Fabrika();

            fabrikam.Uret(urun1);
            fabrikam.Uret(urun2);
        }
    }
}

Senaryo 3 – Üretim Hattına Yeni Bir Ürün Eklendi.

Şimdi bu yeni kodlama yapısında fabrikadaki üretim hattına yeni bir ürün eklendiğinde yapılacak işlemleri aşağıda gösterelim. Her zamanki gibi ilk aşama olarak ürünümüzü ekliyoruz.

namespace UretimSenaryosu
{
    public interface IUrun
    {
        void Uret();
    }

    public class Urun1: IUrun
    {
        public void Uret()
        {
            //Burada ürün 1'e özel üretim metotları çalıştırılıyor.
            Console.WriteLine("Ürün 1'e özel üretim yapıldı.");
        }
    }

    public class Urun2: IUrun
    {
        public void Uret()
        {
            //Burada ürün 2'ye özel üretim metotları çalıştırılıyor.
            Console.WriteLine("Ürün 2'ye özel üretim yapıldı.");
        }
    }

    public class Urun3: IUrun
    {
        public void Uret()
        {
            //Burada ürün 3'e özel üretim metotları çalıştırılıyor.
            Console.WriteLine("Ürün 3'e özel üretim yapıldı.");
        }
    }
}

Fabrika sınıfımızda ve içerisindeki Uret metodunda herhangi bir değişiklik yapmıyoruz. Program.cs içerisine de ürün 3 için üretim yapma kodunu yazıyoruz ve projemizin içerisine yeni ürünümüzü başarı ile eklemiş oluyoruz.

namespace UretimSenaryosu
{
    public class Uretim
    {
        public static void Main(string[] args)
        {
            Urun1 urun1 = new Urun1();
            Urun2 urun2 = new Urun2();
            Urun3 urun3 = new Urun3();

            Fabrika fabrikam = new Fabrika();

            fabrikam.Uret(urun1);
            fabrikam.Uret(urun2);
            fabrikam.Uret(urun3);
        }
    }
}

Görüldüğü üzere projeye yeni ürünü eklemek için sadece ürün tanımlarını içeren sınıfın tanımlanması yeterli oldu. Projede ne kadar fazla karmaşık yapı olursa olsun, proje bu prensibe göre kodlandığında bizim için ileriki bakım aşamaları sancılı olmaktan çıkacaktır.

Faydalı olması dileğiyle…