Refactoring : Table Driven Methods

Değerli arkadaşım Yahya ile ASP.MVC frameworkü pratik olarak öğrenmek için MyMovieCollection adında open-source bir proje geliştiriyoruz. Film koleksiyonu yapmayı sevdiğim için ancak böyle bir proje aklıma geldi. :) Proje basit olarak kullanıcıların filmlerini saklamasını,düzenlemesini .. gibi işlemleri sağlıyor. Geçen gün projede yaptığım basit bir  refactoring if-else, switch-case yapılarından kurtulmak için oldukça güzel bir yöntem olduğu için buraya da yazmak istedim.

Projede her filme kullanıcılar tarafından belirli bir puan verilebiliyor.Listeden seçilen elemana göre Filmlerde verilen puanın düştüğü belirli bir puan aralığına göre “İyi,Kötü,Çok iyi ” tarzı sınıflanıyorlar. Mesela 0-2 arası çok kötü 2-4 arası kötü gibi.Şimdi sınıfı aşağıya yazıp inceleyelim.

public class PointScale
    {
        public const string ALL = "Lütfen Seçiniz";
        public const string VERYBAD = "Çok Kötü";
        public const string BAD = "Kötü";
        public const string NOTBAD = "Fena Sayılmaz";
        public const string GOOD = "İyi";
        public const string EXCELLENT = "Çok iyi";

        private readonly double minPoint;
        private readonly double maxPoint;
        private readonly string scaleName;
        private readonly int scaleID;

        public PointScale(double minPoint, double maxPoint, string scaleName, int scaleID)
        {
            this.minPoint = minPoint;
            this.maxPoint = maxPoint;
            this.scaleName = scaleName;
            this.scaleID = scaleID;
        }

        public static PointScale ParseScale(int scale)
        {
            switch (scale)
            {
                case 1:
                    return new PointScale(0, 1.99, VERYBAD, 1);
                case 2:
                    return new PointScale(2, 3.99, BAD, 2);
                case 3:
                    return new PointScale(4, 5.99, NOTBAD, 3);
                case 4:
                    return new PointScale(6, 7.99, GOOD, 4);
                case 5:
                    return new PointScale(8, 10, EXCELLENT, 5);
                default:
                    return new PointScale(0, 10, ALL, 0);
            }
        }

        public virtual double MinPoint
        {
            get { return minPoint; }
        }

        public virtual double MaxPoint
        {
            get { return maxPoint; }
        }

        public virtual string ScaleName
        {
            get { return scaleName; }
        }

        public virtual int ScaleID
        {
            get { return scaleID; }
        }       
    }

Şimdi yukarıdaki sınıfın ParseScale metoduna bakmanızı istiyorum.Kullanıcı bu metodu çağırarak bir numara veriyor ve bu numaraya karşılık gelen PointScale nesnesi geri dönüyor. Gördüğünüz gibi metod içinde birçok switch-case yapısı mevcut.Şimdi bu switch-case yapılarından nasıl kurtulabiliriz biraz düşünün. Hemen akla gelmesede çok güzel ve basit bir yöntemle bu tarz koşullu yapılardan kolaylıkla kurtulabiliriz. Yeni halini yazalım ve incelemeye devam edelim.

public class PointScale
    {
        public const string ALL = "Lütfen Seçiniz";
        public const string VERYBAD = "Çok Kötü";
        public const string BAD = "Kötü";
        public const string NOTBAD = "Fena Sayılmaz";
        public const string GOOD = "İyi";
        public const string EXCELLENT = "Çok iyi";

       private static readonly IDictionary<int, PointScale> intToPointScale 
            = new Dictionary<int, PointScale>()
                                                {
                                                    {0,new PointScale(0,10,ALL,0)},
                                                    {1,new PointScale(0, 1.99, VERYBAD, 1)},
                                                    {2,new PointScale(2, 3.99, BAD, 2)},
                                                    {3,new PointScale(4,5.99,NOTBAD,3)},
                                                    {4,new PointScale(6,7.99,GOOD,4)},
                                                    {5,new PointScale(8,10,EXCELLENT,5)}
                                                 };
        private readonly double minPoint;
        private readonly double maxPoint;
        private readonly string scaleName;
        private readonly int scaleID;

        public PointScale(double minPoint, double maxPoint, string scaleName, int scaleID)
        {
            this.minPoint = minPoint;
            this.maxPoint = maxPoint;
            this.scaleName = scaleName;
            this.scaleID = scaleID;
        }

        public static PointScale ParseScale(int scale)
        {
            if (intToPointScale.ContainsKey(scale))
                return intToPointScale[scale];

            return new PointScale(0, 10, ALL, 0);
        }

        public virtual double MinPoint
        {
            get { return minPoint; }
        }

        public virtual double MaxPoint
        {
            get { return maxPoint; }
        }

        public virtual string ScaleName
        {
            get { return scaleName; }
        }

        public virtual int ScaleID
        {
            get { return scaleID; }
        }        
    }

Yukarıdaki ParseScale metoduna baktığımızda switch-case yapılarının ortadan kalktığını görüyoruz. Bunu basit olarak verilen numaraya karşılık gelecek olan PointScale nesnelerini tablo mantığı ile bir IDictionary intToPointScale  nesnesinde eşleştirerek yapıyoruz. Ardından metod içinde verilen numaraya karşılık gelen nesne switch-case,if-else kontrolü yapmadan return intToPointScale[scale]; sayesinde geri dönülüyor. Bu şekilde uygulama oldukça esnek hale geldi. Dictionary içinde sakladığımız nesneleri istersek dışarıdan bir XML, yada Veritabanından alabiliriz ve herhangi bir if-else eklemek zorunda kalmayız. Bu tarz Tablo mantığı ile çalışan metodlara Table Driven Methods deniliyor. Çoğu durumda oldukça faydalı olabiliyor.

9 thoughts on “Refactoring : Table Driven Methods

  1. M. Cihat Altuntaş Post author

    Aslında ileride belki 23 pattern hakkında yazmış olacağım.Fakat daha önceden yaptığım bir hata Pattern ezberlemekti bu yüzden genelde Design Pattern konuları ile alakalı ezbere kopyala-yapıştır mantığı ile yazmaktansa gerçek hayatta karşılaştığım problemlere çözüm sunan Pattern’lar hakkında yazmayı tercih ediyorum ve öğrenecek olanlara da bunu tavsiye ediyorum.Bu yüzden yavaş yavaş ortaya çıkması bence daha iyi zaten yeri geldikçe yazıyorum.Tasvsiyen için teşekkürler. Bizi izlemeye devam edin :)

  2. Pingback: Refactoring-4 « Yazılım Mühendisliği

  3. Pingback: Refactoring-4 « Yazılım Mühendisliği

  4. Pingback: Yazılım Mühendisliği » Fluent Interface Örneği

  5. Serhat

    Webde dolaşırken gözüme çarptı. 2 kod parçası birbirinden biraz farklı bu yüzden dikkatli kullanmakta fayda var diye düşünüyorum. İkincisinde tüm oluşabilecek nesneleri ihtiyaç doğmadan önce toptan oluşturuyor ve ihtiyaç olduğunda bunları kullanıyor. İlkinde ise her ihtiyaç doğduğunda nesneyi tekrar tekrar oluşturuyor. Çok fazla kaynak gerektiren sınıflarda sorun olabilir.

  6. M. Cihat Altuntaş Post author

    Gerçekten gerekmeden yapılan performans optimizasyonlarına(premature optimizastion) karşı olsamda yukarıdaki duruma performans olarak bakarsak da bence 2. yöntemdeki yani nesnelerin oluşturulup static dictionary içinde tutulması daha verimli çalışır. Çünkü uygulama bazında bu nesnelerde toplam 5 adet oluşturulacak. Fakat diğer durumda o metod her çağırıldığında (mesela uygulamada 1000 defa çağırıldı) 1000 adet nesne oluşturulacak. Tabi arada birinin static olup bellekte sürekli yer kaplaması diğerinin GC tarafından toplanması farkı var. Onunda problem olacağını düşünmüyorum.

  7. Pingback: » Fluent Interface Örneği

Comments are closed.