Polymorphic Open/Closed Principle Nedir?

Polymorphic Open/Closed Principle

Merhaba. Bu yazımda Polymorphic Open/Closed Principle nedir bundan bahsedeceğim.

Bu yazıyı yazmamın altında yatan sebep; Open/Closed prensibine dair internette birbirinden farklı örneklerin var olması ve bunun yarattığı kafa karışıklığına bir son vermektir. Bu örnek farklılığı ve kafa karışıklığı Open/Closed prensibinin birden fazla kişi tarafından farklı açılardan ele alınmasından kaynaklanmaktadır.

Meyer’ in Open/Closed Prensibi

Open/Closed prensibini ilk ortaya atan Bertrand Meyer’ dir ve bugün bildiğimiz tanımını yapan da odur. Yani, “Bir modül(namespace, sınıf, metod, fonksiyon vb.) gelişime açık, ama değişime kapalı olmalıdır” tanımını Bertrand Meyer yapmıştır.

Meyer için önemli olan modüllerin kalıtıma yani gelişmeye açık olmasıdır. Böylelikle yeni değişiklik talepleri geldikçe, eski modulden kalıtılarak yeni bir modül oluşturulur ve gereken yerlerde bu yeni modül kullanılır.

Örnek verecek olursak; bir apartmanda oturduğumuzu düşünelim ve bu apartmanın yöneticisi için, apartman sakinleri ile arasındaki ilişkileri yönettiği bir yazılım yazdığımızı düşünelim. Yönetici her apartman sakininden oturduğu evin metre karesine göre aidat alsın. Bu apartman sakinlerini temsil eden bir apartman dairesi sınıfımız olsun.

    public class ApartmanDairesi
    {
        public int MetreKare { get; set; }

        public ApartmanDairesi(int metreKare)
        {
            this.MetreKare = metreKare;
        }

        public virtual int AidatHesapla()
        {
            if (this.MetreKare <= 80)
            {
                return this.MetreKare * 10;
            }
            else if (this.MetreKare <= 120)
            {
                return this.MetreKare * 100;
            }
            else
            {
                return this.MetreKare * 1000;
            }
        }
    }

Şimdi gelin toplam aidatı hesaplayan bir sınıf yazalım.

    public class Yonetici
    {
        public ApartmanDairesi[] ApartmanDairesi { get; set; } = new ApartmanDairesi[5];

        public void Olustur()
        {
            ApartmanDairesi[0] = new ApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[1] = new ApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[2] = new ApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[3] = new ApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[4] = new ApartmanDairesi(new Random().Next(200));
        }

        public Yonetici()
        {
            Olustur();
        }
        public int ToplamAidatHesapla()
        {
            var total = 0;
            for (int i = 0; i < ApartmanDairesi.Length; i++)
            {
                total = ApartmanDairesi[i].AidatHesapla();
            }
            return total;
        }
    }

Şimdiye kadar herşey yolunda. Bu şekilde production ortamına çıktık. Uygulamamız çok beğenildi ve 10 apartman daha bu uygulamayı kullanır hale geldi. Ama zaman geçtikçe farklı istekler gelmeye başladığını ve bir değişiklik gerektiğini varsayalım. Diyelim ki, teknoloji ilerledi ve apartman sakinlerinden bazıları artık evlerine güneş paneli taktırmak istiyor. Ama dairesi güneş almayan bazı apartman sakinleri de bunu yaptırmak istemiyor. Herkesten eşit aidat alırsak haksızlık olur. Güneş paneli olan dairelerden metre kareye göre aidat indirimi yapmamız gerekti. Biz de tuttuk bir değişken ekledik, güneş paneli var mı diye ve bunu kontrol ederek aidatımızı hesapladık.

    public class ApartmanDairesi
    {
        public int MetreKare { get; set; }
        public bool GunesPaneliVarmi { get; set; }

        public ApartmanDairesi(int metreKare, bool gunesPaneliVarmi)
        {
            this.MetreKare = metreKare;
            this.GunesPaneliVarmi = gunesPaneliVarmi;
        }

        public int AidatHesapla()
        {
            var aidat = 0;
            if (this.MetreKare <= 80)
            {
                aidat = this.MetreKare * 10;
                if (this.GunesPaneliVarmi)
                {
                    aidat -= 5;
                }
            }
            else if (this.MetreKare <= 120)
            {
                aidat = this.MetreKare * 100;
                if (this.GunesPaneliVarmi)
                {
                    aidat -= 30;
                }
            }
            else
            {
                aidat = this.MetreKare * 1000;
                if (this.GunesPaneliVarmi)
                {
                    aidat -= 120;
                }
            }

            return aidat;
        }
    }

İşte Meyer burda diyor ki; bunun yapılması yanlış. Bizim uygulamamız bir kere production ortamına çıktı ve değişikliğe kapalı hale geldi. Bundan sonra yeni gelen değişiklikler için var olan sınıflar üzerinde birşeyler yapmaya kalkışırsak bu korkunç bir faciaya sebebiyet verebilir ve bu facia hali hazırdaki kullanıcılarımızı etkileyebilir. Peki ne yapmalı da bu potansiyel facianın önüne geçmeli. İşte burda devreye kalıtım giriyor.

ApartmanDairesi sınıfının AidatHesapla metodunu virtual keyword’ ünü kullanarak kalıtıma açık hale getiriyoruz ve yeni versiyonumuzda ApartmanDairesi sınıfından türeyen GunesPanelliApartmanDairesi sınıfını kullanıyoruz.

    public class ApartmanDairesi
    {
        public int MetreKare { get; set; }

        public ApartmanDairesi(int metreKare)
        {
            this.MetreKare = metreKare;
        }

        public virtual int AidatHesapla()
        {
            if (this.MetreKare <= 80)
            {
                return this.MetreKare * 10;
            }
            else if (this.MetreKare <= 120)
            {
                return this.MetreKare * 100;
            }
            else
            {
                return this.MetreKare * 1000;
            }
        }
    }
    public class GunesPanelliApartmanDairesi : ApartmanDairesi
    {
        public GunesPanelliApartmanDairesi(int metreKare) : base(metreKare)
        {

        }

        public override int AidatHesapla()
        {
            var aidat = base.AidatHesapla();
            if (this.MetreKare <= 80)
            {
                aidat -= 5;
            }
            else if (this.MetreKare <= 120)
            {
                aidat -= 30;
            }
            else
            {
                aidat -= 120;
            }
            return aidat;
        }
    }

Böylelikle yaptığımız değişiklik kullanıcılarımızı etkilemeyecek hale geldi. Ama yapmamız gereken birşey daha var. Yönetici sınıfı hala eski ApartmanDairesi sınıfını kullanıyor. Burda değişiklik yapmalıyız ama yonetici sınıfı da production ortamında ve kullanımda olduğu için bu sınıfında türetilmesi ve yeni bir yönetici sınıfının oluşturulması gerekiyor. Bunu yaparken de Olustur() metodunun virtual olarak yazılıp kullanılması gerekiyor. Ben bu sınıfa YoneticiV2 diyeceğim.

    public class Yonetici
    {
        public ApartmanDairesi[] ApartmanDairesi { get; set; } = new ApartmanDairesi[5];

        public virtual void Olustur()
        {
            ApartmanDairesi[0] = new ApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[1] = new ApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[2] = new ApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[3] = new ApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[4] = new ApartmanDairesi(new Random().Next(200));
        }

        public Yonetici()
        {
            Olustur();
        }
        public int ToplamAidatHesapla()
        {
            var total = 0;
            for (int i = 0; i < ApartmanDairesi.Length; i++)
            {
                total = ApartmanDairesi[i].AidatHesapla();
            }
            return total;
        }
    }
    public class YoneticiV2 : Yonetici
    {

        public override void Olustur()
        {
            ApartmanDairesi[0] = new ApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[1] = new GunesPanelliApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[2] = new ApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[3] = new GunesPanelliApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[4] = new ApartmanDairesi(new Random().Next(200));
        }

        public YoneticiV2() :base()
        {

        }
    }

İşte bu kadar. Yeni versiyonda her apartman yeni sınıfları kullanmak zorunda değil. İsteyen eski versiyonu kullanır. Yani Yonetici.ToplamAidatHesapla der. İsteyen yeni versiyona geçebilir ve YoneticiV2.ToplamAidatHesapla diyebilir.

İşte Meyer’ in bahsettiği Open/Closed prensibi budur.

Polymorphic Open/Closed Principle

Robert C. Martin’ in öncülerinden olarak bir makalesinde kaleme aldığı Open/Closed Principle ise daha çok polymorphism üzerine inşa edilmiş bir prensiptir.

Robert C. Martin’ e göre implementasyonun tekrar override edilip kullanılmasına gerek yoktur. Yani ApartmanDairesi sınıfının extend edilerek yeni bir GunesPanelliApartmanDairesi sınıfı oluşturmaya ve ApartmanDairesi sınıfı içindeki var olan içi dolu AidatHesapla metodunu override etmeye gerek yoktu. Bunun yerine bir abstract base class kullanılması ve yeni implementasyonların bu base class dan türetilmesi gerekirdi. Yani;

    public abstract class Daire
    {
        public int MetreKare { get; set; }
        public abstract int AidatHesapla();
    }   

    public class KucukApartmanDairesi : Daire
    {
        public KucukApartmanDairesi(int metreKare)
        {
            this.MetreKare = metreKare;
        }

        public override int AidatHesapla()
        {
            return this.MetreKare * 10;
        }
    }

    public class OrtaApartmanDairesi : Daire
    {
        public OrtaApartmanDairesi(int metreKare)
        {
            this.MetreKare = metreKare;
        }

        public override int AidatHesapla()
        {
            return this.MetreKare * 100;
        }
    }

    public class BuyukApartmanDairesi : Daire
    {
        public BuyukApartmanDairesi(int metreKare)
        {
            this.MetreKare = metreKare;
        }

        public override int AidatHesapla()
        {
            return this.MetreKare * 1000;
        }
    }

    public class GunesPanelliKucukApartmanDairesi : Daire
    {
        public GunesPanelliKucukApartmanDairesi(int metreKare)
        {
            this.MetreKare = metreKare;
        }

        public override int AidatHesapla()
        {
            return this.MetreKare * 10 - 5;
        }
    }

    public class GunesPanelliOrtaApartmanDairesi : Daire
    {
        public GunesPanelliOrtaApartmanDairesi(int metreKare)
        {
            this.MetreKare = metreKare;
        }

        public override int AidatHesapla()
        {
            return this.MetreKare * 100 - 30;
        }
    }

    public class GunesPanelliBuyukApartmanDairesi : Daire
    {
        public GunesPanelliBuyukApartmanDairesi(int metreKare)
        {
            this.MetreKare = metreKare;
        }

        public override int AidatHesapla()
        {
            return this.MetreKare * 1000 - 120;
        }
    }

Tabi aynı durum Yonetici sınıfımız içinde geçerli. Yonetici sınıfımızdaki implementasyonların tekrar override edilmesine gerek yok. Bunun yerine Yonetici sınıfı bir abstract base class’dan türetilir ve böyle kullanılabilir. Ben örneğimde türettiğim sınıflara YoneticiV1, YoneticiV2 vb isimler vereceğim.

    public abstract class Yonetici
    {
        public Daire[] ApartmanDairesi { get; set; } = new Daire[3];
        public abstract void Olustur();
        public int ToplamAidatHesapla()
        {
            var total = 0;
            for (int i = 0; i < ApartmanDairesi.Length; i++)
            {
                total = ApartmanDairesi[i].AidatHesapla();
            }
            return total;
        }
    }
public class YoneticiV1 : Yonetici
    {
        public override void Olustur()
        {
            ApartmanDairesi[0] = new KucukApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[1] = new OrtaApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[2] = new BuyukApartmanDairesi(new Random().Next(200));
        }

        public YoneticiV1()
        {
            Olustur();
        }
    }
    public class YoneticiV2 : Yonetici
    {
        public override void Olustur()
        {
            ApartmanDairesi[0] = new GunesPanelliKucukApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[1] = new GunesPanelliOrtaApartmanDairesi(new Random().Next(200));
            ApartmanDairesi[2] = new GunesPanelliBuyukApartmanDairesi(new Random().Next(200));
        }

        public YoneticiV2()
        {
            Olustur();
        }
    }

Böylelikle yeni implementasyonlar kalıtım aracılığı ile tekrar tekrar kullanılmamış oldu. YoneticiV1 sınıfı var olan ve implemente edilmiş bir Olustur metodunu kullanmadı. Bunun yerine içi boş bir abstract metodu implement etti.

Sonuç

Sonuç olarak Meyer’in open closed prensibi zaman içinde değişikliğe uğramıştır. Her ne kadar Meyer’in tanımı da bir bakıma içinde polymorphism barındırsa da, Robert C. Martin in yaklaşımı daha nesne yönelimli ve polymorphic tir.

Bu makaleminde sonuna gelmiş bulunuyoruz. Ben kaynak olarak wikipedia’ yı kullandım. Link’ e buradan ulaşabilirsiniz.

Umarım çok karıştırmadan doğru örnekler ile aklımdakini ifade edebilmişimdir.

Herkese başarılar 🙂

Bir yorum bırakın