SOLID Prensipleri : 02 Open Closed Principle

By Burak Tungut - 17.4.2016 - Kategori Design / Architectural Patterns

Open Closed PrincipleHerkese selamlar,

Havaların ısındığı şu güzel günlerde gezip tozmanın yanısıra birazda sizlerle paylaşım yapmak istedim. Ancak işin kötü tarafı bir kaç saate sahile inip orada kitap okumak gibi bir planım var, umarım bu nedenle makaleyi aceleye getirmem. Getirirsemde şimdiden affola :)

Geçtiğimiz aylarda SOLID prensiplerini anlatacağım bir makale dizisine başlamıştık. Bu makale ile 2. prensibimiz olan Open Closed Principle'a değineceğiz.
Prensibin bize anlatmak istediği şeyi şu şekilde ifade edebiliriz; "modüller gelişime açık, değişime kapalı olmalıdır".

Hiç şüphesiz modüler yapıların inşaa edildiği mimarilerde en çok kullanılan prensiplerden biri Open-Closed olup, yanına DI framework'ler ya da abstract factory implementasyonları kullanılır. Şimdi bu teorik yaklaşımları biraz uygulanabilir, el ile tutulabilir hale getirelim.

Problemi Tanıyalım

Çok ilginç örnekler ile implementasyonlar yapmaya bayılırım, bilirsiniz :) Bu prensibi anlatırken özellikle ilkokul ve lisede çokça rastladığımız faiz problemlerinden esinlendim. Hatta içerdiği bilinmeyenlerin (a, n, t) birleşimini alarak Ant Formül'ü diyede bir isme sahiptir. Sizlerde benim gibi yıllardır bu formülü kullanmamış olabilirsiniz. Onun için öncelikle faiz problemlerini çözmemize yardımcı olan Ant Formül'ünü bir hatırlayalım.

F = (a * n * t) / x

Bu formülde F faiz miktarını, a anaparayı, n faiz yüzdesini ve t zaman birimini gösterirken x ise yıllık, aylık, haftalık ya da günlük periyotlar olmak üzere sırasıyla 100, 1200, 5200 ve 36000 değerini alan bir bilinmeyendir.

Open-Closed Prensipsiz Uygulanışı

Prensip ya da prensipsiz eminim yüz farklı şekilde bu problemi çözecek bir algoritma inşaa edilebilir. Ben aklıma gelen ilk yöntemi seçiyorum ve öncelikle aşağıdaki gibi bir enumeration yaratıyorum.

public enum Interval
{
    Yearly,
    Montly
}

Arından InterestCalculator adında bir class ve içerisinde Calculate adında bir method yaratıyorum. Methodumuz parametre olarak a, n, t bilinmeyenlerini ve bir Interval alıyor, geriye ise decimal tipinde bir değer döndürüyor olsun. Tıpkı aşağıdaki gibi;

public class InterestCalculator
{
    public decimal Calculate(int a, int n, int t, Interval interval)
    {
        decimal retVal = 0;

        if (interval == Interval.Yearly)
        {
            return (a * n * t) / 100;
        }
        else if (interval == Interval.Montly)
        {
            return (a * n * t) / 1200;
        }

        return retVal;
    }
}

Harika! Elimizde Yıllık ve haftalık periyotlarda ki faizi hesaplayabilecek bir methodumuz var.
Uygulamada kullanmaya başlayabiliriz. Aradan bir kaç hafta geçiyor ve iş birimleri şimdide haftalık periyoda sahip faizlerinden hesaplanacağı bir geliştirme istiyor. O halde Interval adlı enumeration'a Weekly adlında bir member daha ekliyor ve buna karşılık gelecek bir if-clause'u Calculate methoduna da ekliyoruz. Sonuçta elimizdeki yapı aşağıdaki gibi olacaktır;

public class InterestCalculator
{
    public decimal Calculate(int a, int n, int t, Interval interval)
    {
        decimal retVal = 0;

        if (interval == Interval.Yearly)
        {
            return (a * n * t) / 100;
        }
        else if (interval == Interval.Montly)
        {
            return (a * n * t) / 1200;
        }
        else if (interval == Interval.Weekly)
        {
            return (a * n * t) / 5200;
        }

        return retVal;
    }
}

public enum Interval
{
    Yearly,
    Montly,
    Weekly
}

Evet yaptığımız bu geliştirme ile istekler karşılanacaktır ama Open-Closed Principle'ın dayandığı tek ve önemli ilkeden olduğu gibi zıt yönde hareket ediyoruz. Yaptığımız herşey istenilenin aksine değişime açık durumda. Öyle bir yapı inşaa etmeliyiz ki core işimizi yürüten Calculate methodunda hiç bir değişiklik yapmamalıyız. Öyleyse aynı yapıyı bir de Open-Closed Principle'a uygun şekilde inşaa edelim.

Open-Closed Prensipli Uygulanışı

Her yiğidin yoğurt yiyişi kesinlikle farklıdır. Ben öyle bir yapı düşündüm ki; Calculate methodu a, n, t parametrelerini alırken bir de dördüncü parametre olarak soyut (abstract class ya da interface) bir tip kabul etsin. Böylece periyot bilgisi soyut bir tipten geliyor olup, kimin iplemente ettiği hakkındaki bilgi ile ilgilenmiyor olacağız. Öyleyse soyut bir tipede ihtiyacımız var. Bu tip sadece periyot bilgisini döndürecek bir property ya da method'un implemente edildiğinin garantisini vermeli. Aşağıdaki gibi bir tip işimizi görecektir;

public abstract class BaseInterest
{
    public abstract int IntervalScore { get; }
}

BaseInterest tipi ile istediğimiz kadar periyot tipini implemente edebiliriz. Örneğin yıllık periyot için bir implementasyon aşağıdaki gibi olacaktır;

public class YearlyInterest : BaseInterest
{
    public override int IntervalScore
    {
        get { return 100; }
    }
}

Hal böyleyken InterestCalculator class'ımız ve Calculate methodumuz aşağıdaki gibi bir hal alabilir;

public class InterestCalculator
{
    public decimal Calculate(int a, int n, int t, BaseInterest interestImplementation)
    {
        return (a * n * t) / interestImplementation.IntervalScore;
    }
}

Artık Calculate methodumuz periyot bilgisini alabilmek için sadece soyut bir tip'e bağımlı. Diğer bir değiş ile kimin implemente ettiği bilgisi ile ilgilenmiyor ve bu tiplere bağımlı durumda değil. Böylece hiç Calculate methodunda değişiklik yapmadan yüzlerce BaseInterest implementasyonu yaratarak çeşit çeşit periyot tiplerinden faydalanabiliriz.

Uygulamanın son hali ile Calculate methodu artık "gelişime açık, değişime kapalı" olarak nitelendirilebilir. Hatta BaseInterest'i miras alan tiplerimizi bambaşka assembly'ler içerisinde tutabilir ve ister reflection yardımıyla istersek DI framework'ler ile runtime'da inject edebiliriz. Bir de araya akışı Abstract Factory Pattern ile değiştirebilecek bir yapı eklersek tadından yenmeyecek bir mimariye sahip olabiliriz.

O halde sizlere iyi çalışmalar diliyorum. Esen kalın :)

Yorum Bırak

Facebook
Son Yorumlar