21 Ekim 2013 Pazartesi

Refactoring:Tip Kodlarını Alt Sınıflarla Değiştirin !

Yazılımın domain katmanı tasarlanırken,gerçek hayattaki nesneleri, sınıflar yardımı modellediğimiz aşikardır.
Yanlış bir tasarım/modelleme yapıldığında,düzeltmesi yeniden yapılandırması güçleşir.

Sistematik düşünüp kod geliştirme işini, bir çok dinozor yazılımcı angarya,zaman kaybı olarak algılar.Halbu ki ileri de daha fazla zaman kaybedeceğinin farkında değildir...


Şimdi aşağıda ki örneği inceleyecek olursak;
Bir firmadaki Personelleri görevlerine göre modelleğimiz bir örnek.
Firmada ki personeller görevlerine göre yazılım geliştirici,satışcı,destekçi,müdür gibi farklı görevleri olan personeller var.

Bu personellerin hepsini bir Person sınıfı ile ifade etmek karışıklığa yol açabilir.
Neden derseniz ?
Çünkü her bir personel tipinin temelde bazı özellikleri aynı olsa da,farklı olan özellikleri de olabileceği için tek bir Personel sınıfı ile ifade etmek yanlış,yetersiz,karmaşık ve gereksiz olacaktır. :)

Her ne kadar PersonType gibi bir enum kullanarak, Person sınıfı ayırt etmeye çalışsak da buda yetersiz kalacaktır.

Karmaşıklığa bir örnek verecek olursak;
Aşağıdaki methodları Person sınıfına eklediğimizi düşünelim.
public void kodGelistir(); -> Yazılımcının işidir. 
public void satisYap();    ->  Satışcının işidir.
Bu methodların implementasyonu başarılı bir şekilde çalışsa da mantıken Person sınıfında olması yanlıştır.Person sınıfını gereksiz yere kalabalık eder....

O sebeble public void kodGelistir(); methodunun Developer sınıfına eklememiz gerekir.
Aynı şekilde public void satisYap();  methodunun da Salesman sınıfına eklememiz gerekir.
Bu şekilde yazılımcı Personel den satış yapmasını,satışcı personelden de kod geliştirmesini beklememiş olursunuz ! ;)

Buraya kadar yaptığımız işleme "Replace Type Code with Subclasses" refactoring prensibi deniliyor...

Person.cs
using System;

public enum PersonType
{
    DEVELOPER = 1,
    SALESMAN = 2,
    MANAGER = 3,
    SUPPORTER = 4
}

public abstract class Person
{
    public int id { get; set; }
    public string firstName { get; set; }
    public string lastName { get; set; }
    public double salary { get; set; }

    public PersonType personType { get; set; }

    public Person(PersonType type)
    {
        this.personType = type;
    }

    public static Type getType()
    {
        return typeof(Person);
    }
}

Developer.cs
using System;

public class Developer : Person
{
    public Developer()
        : base(PersonType.DEVELOPER)
    {
        this.salary = 10.000;
    }

    public static Type getType()
    {
        return typeof(Developer);
    }
}

Manager.cs
using System;

public class Manager : Person
{
    public Manager()
        : base(PersonType.MANAGER)
    {
        this.salary = 1500;
    }

    public static Type getType()
    {
        return typeof(Manager);
    }
}

Salesman.cs
using System;

public class Salesman : Person
{
    public Salesman()
        : base(PersonType.SALESMAN)
    {
        this.salary = 1500;
    }

    public static Type getType()
    {
        return typeof(Salesman);
    }
}

Supporter.cs
using System;

public class Supporter : Person
{
    public Supporter()
        : base(PersonType.SUPPORTER)
    {
        this.salary = 1000;
    }

    public static Type getType()
    {
        return typeof(Supporter);
    }
}

Diğer bir mevzu ise yazılan kodda ki iç içe bloklu yapılardır.Bu örnek olduğu için göze okunabilir gözükse de complex bir sistemde ne demek istediğimi çok iyi anlayacaksınız.

Aşağıdaki PersonFactory sınıfına ait iki adet method bulunmakta bunlar ;
public Person cleanCreate(PersonType personType); // factory method
public Person dirtyCreate(PersonType personType); // factory method

İki method da parametre ile verilen enum tipinden, ilgili tipi create edip döndürüyor.

dirtyCreate methodu içersinde switch(condition) yapısı kullanılarak ilgili enum tipinden,
enuma karşılık gelen sınıftan bir intance oluşturuluyor.

cleanCreate methodu içersinde ise map'leme yöntemi ile,ilgili enum tipinden class tipini bulup activator sınıfı ile tipden bir instance oluşturuluyor.

cleanCreate methodu içersinde herhangi bir condition(if,switch vs) kullanmadan map'leme yöntemi ile nesne oluşturma işine de "Replacing Conditionals With Map" işlemi deniliyor.


PersonFactory.cs
using System;
using System.Collections.Generic;

public class PersonFactory
{
    private static object _lock = new object();

    private static PersonFactory instance;

    private static Dictionary map = new Dictionary();

    private PersonFactory()
    {
        this.initializeMap();
    }


    public static PersonFactory getInstance()
    {
        if (instanceIsNull())
            createInstance();
        return instance;
    }

    private static void createInstance()
    {
        lock (_lock)
        {
            if (instanceIsNull())
                instance = new PersonFactory();
        }
    }

    private static bool instanceIsNull()
    {
        return instance == null;
    }

 // Replace Conditional With Map işlemi.
    private void initializeMap()
    {
        map.Add(PersonType.DEVELOPER, Developer.getType());
        map.Add(PersonType.SALESMAN, Salesman.getType());
        map.Add(PersonType.MANAGER, Manager.getType());
        map.Add(PersonType.SUPPORTER, Supporter.getType());
    }


    // iyi bir factory method yazım şekli...
    public Person cleanCreate(PersonType personType)
    {
        Type type;
        map.TryGetValue(personType, out type);
        object instance = Activator.CreateInstance(type);
        return (Person)instance;
    }

    // kötü bir factory method yazım şekli...
    // aşağıdaki swicth yapısındaki şartlı instance oluşturma işlemi,
    // initializeMap methodu içerinde map'leme yöntemi ile değiştirilmiştir.
    public Person dirtyCreate(PersonType personType)
    {
        Person person = null;
        switch (personType)
        {
            case PersonType.DEVELOPER:
                person = new Developer();
                break;
            case PersonType.SALESMAN:
                person = new Salesman();
                break;
            case PersonType.MANAGER:
                person = new Manager();
                break;
        }
        return person;
    }
}

Test sınıfına bakacak olursak ;
PersonFactoryTest.cs
using System;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;


[TestClass]
public class PersonFactoryTest
{
    private PersonFactory factory;

    [TestInitialize]
    public void init()
    {
        factory = PersonFactory.getInstance();
    }

    [TestMethod]
    public void createDeveloperTest()
    {
        //Yazılımcıda bir personeldir (!)
        Person person1 = factory.cleanCreate(PersonType.DEVELOPER);
        person1.id = 1;
        person1.firstName = "ismail";
        person1.lastName = "Kocacan";
    }


    [TestMethod]
    public void createManagerTest()
    {
        //Müdür de bir personeldir...
        Person person2 = factory.cleanCreate(PersonType.MANAGER);
        person2.id = 2;
        person2.firstName = "ali veli";
        person2.lastName = "deli";
    }


    [TestMethod]
    public void createSalesmanTest()
    {
        //Satışcıda bir personeldir...
        Person person3 = factory.cleanCreate(PersonType.SALESMAN);
        person3.id = 3;
        person3.firstName = "adı lazım değil";
        person3.lastName = "sanane";
    }



    [TestMethod]
    public void createSupporterTest()
    {
        // Destekçide bir personeldir...
        Person person4 = factory.cleanCreate(PersonType.SUPPORTER);
        person4.id = 4;
        person4.firstName = "kezban1";
        person4.lastName = "kezban1";
    }


    [TestCleanup]
    public void finalize()
    {
        factory = null;
    }
}

iyi çalışmalar