среда, 7 сентября 2011 г.

DRY: Part 4 - Conditional logic duplication

Другие части эпической саги о вреде дублирования:

Этот вид дублирования не так очевиден, как предыдущие - потому что повторяется лишь условная логика, управляющая потоком выполнения приложения. Выполняемые при этом операции зачастую совершенно различны.

Нет ничего плохого в том, чтобы написать блок switch один раз (часто я так поступаю при создании объектов с помощью фабрики). Но повторение одних и тех же условий - не сулит ничего хорошего. Например, расчёт зарплат и бонусов зависит от типа сотрудника:

public abstract class Employee : Entity<int>
{
    private string _name;
    private decimal _hourlyPayRate;
    private decimal _annualSalary;
    private decimal _tax;
    private EmployeeType _employeeType;

    public decimal CalculatePayment()
    {
        decimal result = CalculateWeeklyPayment() - _tax;

        if (DateTime.Today.Month == 12)
            result += CalculateChristmasBonus();

        return result;
    }

    private decimal CalculateWeeklyPayment()
    {
        switch (_employeeType)
        {
            case EmployeeType.Hourly:
                const int hoursInPayPeriod = 40;
                return hoursInPayPeriod * _hourlyPayRate;
            case EmployeeType.Salaried:
                const int payPeriodsInYear = 52;
                return _annualSalary / payPeriodsInYear;
            case EmployeeType.Zombie:
                return 0;
            default:
                throw new InvalidEnumArgumentException();
        }
    }

    private decimal CalculateChristmasBonus()
    {
        switch (_employeeType)
        {
            case EmployeeType.Salaried:
                return _annualSalary / 12;
            default:
                return 50;
        }
    }
}

Условная логика хранит знания о различных типах сотрудников. Повторение условий приводит к дублированию этих знаний. Поэтому при добавлении нового типа сотрудника прийдётся найти и обновить все повторяющиеся условия. Устранить такое дублирование можно с помощью полиморфизма (рефакторинг Replace Conditional with Polymorphism):

public abstract class Employee : Entity<int>
{
    private string _name;
    private decimal _tax;

    public decimal CalculatePayment()
    {
        decimal result = CalculateWeeklyPayment() - _tax ;

        if (DateTime.Today.Month == 12)
            result += CalculateChristmasBonus();

        return result;
    }

    protected abstract decimal CalculateWeeklyPayment();

    protected virtual decimal CalculateChristmasBonus()
    {
        return 50;
    }
}

public class HourlyEmployee : Employee
{
    private decimal _hourlyPayRate;

    protected override decimal CalculateWeeklyPayment()
    {
        const int hoursInPayPeriod = 40;
        return _hourlyPayRate * hoursInPayPeriod;
    }
}

public class SalariedEmployee : Employee
{
    private decimal _annualSalary;

    protected override decimal CalculateWeeklyPayment()
    {
        const int payPeriodsInYear = 52;
        return _annualSalary / payPeriodsInYear;
    }

    protected override decimal CalculateChristmasBonus()
    {
        return _annualSalary / 12;
    }
}

public class Zombie : Employee
{
    protected override decimal CalculateWeeklyPayment()
    {
        return 0;
    }
}

К слову, тип объекта не всегда явно выражается кодом типа. Это может быть и условное выражение:

private decimal CalculateChristmasBonus()
{
    if (_annualSalary > 0) // same as EmployeeType.Salaried
        return _annualSalary / 12;

    return 50;
}

Комментариев нет:

Отправить комментарий