Заменить условное выражение рефакторингом полиморфизма или подобный?

Я попытался спросить вариант этого вопроса прежде. Я получил некоторые полезные ответы, но тем не менее ничто, что чувствовало себя совершенно правильным мне. Это кажется мне, это не должно действительно быть то, что трудно гайка для взламывания но я не могу найти изящное простое решение. (Вот мое предыдущее сообщение, но попытайтесь посмотреть на проблему, указанную здесь как процессуальный кодекс сначала, чтобы не быть под влиянием более раннего объяснения, которое, казалось, привело к очень сложным решениям: Шаблон разработки для приложения для калькулятора стоимости?)

В основном проблема состоит в том, чтобы создать калькулятор в течение часов, необходимых для проектов, которые могут содержать много сервисов. В этом случае "запись" и "анализ". Часы вычисляются по-другому для различных сервисов: запись вычисляется путем умножения "на продукт" уровень часа с количеством продуктов, и чем больше продуктов включено в проект, тем ниже уровень часа, но общее количество часов прогрессивно накапливается (т.е. для проекта среднего размера Вы берете и маленькую оценку диапазона и затем добавляете оценку среднего диапазона до количества фактических продуктов). Принимая во внимание, что для анализа это намного более просто, это - просто оптовый тариф для каждого диапазона размера.

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

Я думал с точки зрения фабрики, стратегических моделей и шаблонов "декоратор", но не могу заставить никого работать хорошо. (Я прочитал Главные Первые Шаблоны разработки некоторое время назад, и и шаблоны "декоратор" и описанные шаблоны "фабрика" имеют некоторые общие черты этой проблеме, но я испытываю затруднения при наблюдении их как хорошие решения, как указано там. Пример декоратора казался очень сложным там для того, чтобы просто добавить приправы, но возможно он мог работать лучше здесь, я не знаю. По крайней мере, то, что вычисление часов накапливается прогрессивно, заставляло меня думать о шаблоне "декоратор"... И пример шаблона "фабрика" из книги с фабрикой пиццы... хорошо это просто, кажется, создает такой смешной взрыв классов, по крайней мере, в их примере. Я нашел хорошее использование для шаблонов "фабрика" прежде, но я не вижу, как я мог использовать его здесь, не получая действительно сложный набор классов),

Главная цель состояла в том, чтобы только измениться в одном месте (слабая связь и т.д.), если бы я должен был добавить, новый параметр (скажите что другой размер, как XSMALL и/или другой сервис, как "администрация"). Вот пример процессуального кодекса:

public class Conditional
{
    private int _numberOfManuals;
    private string _serviceType;
    private const int SMALL = 2;
    private const int MEDIUM = 8;

    public int GetHours()
    {
        if (_numberOfManuals <= SMALL)
        {
            if (_serviceType == "writing")
                return 30 * _numberOfManuals;
            if (_serviceType == "analysis")
                return 10;
        }
        else if (_numberOfManuals <= MEDIUM)
        {
            if (_serviceType == "writing")
                return (SMALL * 30) + (20 * _numberOfManuals - SMALL);
            if (_serviceType == "analysis")
                return 20;
        }
        else //i.e. LARGE
        {
            if (_serviceType == "writing")
                return (SMALL * 30) + (20 * (MEDIUM - SMALL)) + (10 * _numberOfManuals - MEDIUM);
            if (_serviceType == "analysis")
                return 30;
        }
        return 0; //Just a default fallback for this contrived example
    }
}

Все ответы ценятся! (Но поскольку я заявил в своих предыдущих сообщениях, что буду ценить фактические примеры кода, а не просто "Пробуют этот шаблон", потому что, поскольку я упомянул, из-за именно это я испытываю затруднения...), я надеюсь, что у кого-то есть действительно изящное решение этой проблемы, что я на самом деле думал с начала, будет действительно просто...

========================================================

НОВОЕ ДОПОЛНЕНИЕ:

Я ценю все ответы до сих пор, но я все еще не вижу действительно простого и гибкого решения проблемы (один, я думал, не будет очень сложно сначала, но по-видимому). Может также случиться так, что я правильно еще не вполне понял каждый ответ. Но я думал, что отправлю свою текущую попытку разработки его (с некоторой справкой от чтения всех различных углов в ответах здесь). Скажите мне, если я на правильном пути или нет. Но по крайней мере теперь такое чувство, что это начинает становиться более гибким... Я могу довольно легко добавить новые параметры, не имея необходимость изменяться в большом количестве мест (я думаю!), и условная логика все в одном месте. У меня есть часть его в xml для получения основных данных, который упрощает часть проблемы, и часть его является попыткой решения для типа стратегии.

Вот код:

 public class Service
{
    protected HourCalculatingStrategy _calculatingStrategy;
    public int NumberOfProducts { get; set; }
    public const int SMALL = 3;
    public const int MEDIUM = 9;
    public const int LARGE = 20;
    protected string _serviceType;
    protected Dictionary _reuseLevels;

    protected Service(int numberOfProducts)
    {
        NumberOfProducts = numberOfProducts;
    }

    public virtual decimal GetHours()
    {
        decimal hours = _calculatingStrategy.GetHours(NumberOfProducts, _serviceType);
        return hours;
    }
}

public class WritingService : Service
{
    public WritingService(int numberOfProducts)
        : base(numberOfProducts)
    {
        _calculatingStrategy = new VariableCalculatingStrategy();
        _serviceType = "writing";
    }
}

class AnalysisService : Service
{
    public AnalysisService(int numberOfProducts)
        : base(numberOfProducts)
    {
        _calculatingStrategy = new FixedCalculatingStrategy();
        _serviceType = "analysis";
    }
}

public abstract class HourCalculatingStrategy
{
    public abstract int GetHours(int numberOfProducts, string serviceType);

    protected int GetHourRate(string serviceType, Size size)
    {
        XmlDocument doc = new XmlDocument();
        doc.Load("calculatorData.xml");
        string result = doc.SelectSingleNode(string.Format("//*[@type='{0}']/{1}", serviceType, size)).InnerText;
        return int.Parse(result);
    }
    protected Size GetSize(int index)
    {
        if (index < Service.SMALL)
            return Size.small;
        if (index < Service.MEDIUM)
            return Size.medium;
        if (index < Service.LARGE)
            return Size.large;
        return Size.xlarge;
    }
}

public class VariableCalculatingStrategy : HourCalculatingStrategy
{
    public override int GetHours(int numberOfProducts, string serviceType)
    {
        int hours = 0;
        for (int i = 0; i < numberOfProducts; i++)
        {
            hours += GetHourRate(serviceType, GetSize(i + 1));
        }
        return hours;
    }
}

public class FixedCalculatingStrategy : HourCalculatingStrategy
{
    public override int GetHours(int numberOfProducts, string serviceType)
    {
        return GetHourRate(serviceType, GetSize(numberOfProducts));
    }
}

И простая форма в качестве примера, которая называет его (я предполагаю, что у меня мог также быть класс Проекта обертки со Словарем, содержащим Объекты службы, но я не добрался до того):

    public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        List quantities = new List();

        for (int i = 0; i < 100; i++)
        {
            quantities.Add(i);
        }
        comboBoxNumberOfProducts.DataSource = quantities;
    }


    private void CreateProject()
    {
        int numberOfProducts = (int)comboBoxNumberOfProducts.SelectedItem;
        Service writing = new WritingService(numberOfProducts);
        Service analysis = new AnalysisService(numberOfProducts);

        labelWriterHours.Text = writing.GetHours().ToString();
        labelAnalysisHours.Text = analysis.GetHours().ToString();
    }
    private void comboBoxNumberOfProducts_SelectedIndexChanged(object sender, EventArgs e)
    {
        CreateProject();
    }

}

(Я не смог включать xml, потому что он был автоматически отформатирован на этой странице, но это - в основном просто набор элементов с каждым сервисным типом и каждым сервисным типом, содержащим размеры с уровнями часа как значения.)

Я не уверен, проталкиваю ли я просто проблему к XML-файлу (я должен был бы все еще добавить новые элементы для каждого нового servicetype и добавить элементы для любого нового размера в каждом servicetype, если это изменилось.), Но возможно невозможно достигнуть того, что я пытаюсь сделать и не имею, чтобы сделать, по крайней мере, тот тип изменения. Используя базу данных, а не xml изменение было бы так же просто как добавление поля и строки:

ServiceType маленький большой носитель

Запись 125 100 60

Анализ 56 104 200

(Просто отформатированный как "таблица" здесь, хотя столбцы не довольно выровненные... Я не являюсь лучшим при проектировании баз данных, хотя, и возможно оно должно быть сделано по-другому, но Вы получаете идею...),

Скажите мне, что Вы думаете!

10
задан 10 revs, 2 users 98% 23 May 2017 в 12:13
поделиться

5 ответов

Я бы начал с перечисления ProjectSize {Small, Medium, Large} и простой функции, возвращающей соответствующее перечисление при заданном numberOfManuals. Оттуда я бы написал разные ServiceHourCalculators , WritingServiceHourCalculator и AnalysisServiceHourCalculator (потому что их логика существенно отличается). Каждый из них принимает numberOfManuals, ProjectSize и возвращает количество часов. Я бы, вероятно, создал карту из строки в ServiceHourCalculator, так что я мог бы сказать:

ProjectSize projectSize = getProjectSize(_numberOfManuals);
int hours = serviceMap.getService(_serviceType).getHours(projectSize, _numberOfManuals);

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

Обновление Я знаю Java, а не C # (очень хорошо), так что это может быть не на 100% правильно, но создание карты будет примерно таким:

Map<String, ServiceHourCalculator> serviceMap = new HashMap<String, ServiceHourCalculator>();
serviceMap.put("writing", new WritingServiceHourCalculator());
serviceMap.put("analysis", new AnalysisServiceHourCalculator());
6
ответ дан 4 December 2019 в 00:23
поделиться

Хорошим началом было бы извлечь условный оператор в метод (хотя и небольшой метод) и дать ему действительно явное имя. Затем извлеките логику внутри оператора if в их собственные методы - снова с действительно явными именами. (Не волнуйтесь, если имена методов длинные - пока они делают то, что они вызываются)

Я бы записал это в коде, но вам лучше выбрать имена.

Затем я бы перешел к более сложным методам и шаблонам рефакторинга. Только когда вы посмотрите на серию вызовов методов, вам покажется целесообразным начать применять шаблоны и т. Д.

Сделайте своей первой целью написать чистый, легкий для чтения и понимания код. Легко увлечься шаблонами (исходя из опыта), но их очень сложно применить, если вы не можете описать существующий код в абстракциях.

РЕДАКТИРОВАТЬ: Итак, чтобы уточнить - вы должны стремиться к тому, чтобы ваш оператор if выглядел так

if( isBox() )
{
    doBoxAction();
}
else if( isSquirrel() )
{
    doSquirrelAction();
}

Как только вы это сделаете, на мой взгляд, будет легче применить некоторые из шаблонов, упомянутых здесь. Но если у вас все еще есть вычисления и т. Д. В вашем выражении if, тогда сложнее увидеть лес из-за деревьев, поскольку вы находитесь на слишком низком уровне абстракции.

2
ответ дан 4 December 2019 в 00:23
поделиться

Я бы выбрал производную от шаблона стратегии. Это добавляет дополнительные классы, но в долгосрочной перспективе его легче поддерживать. Также имейте в виду, что здесь все еще есть возможности для рефакторинга:

public class Conditional
{
    private int _numberOfManuals;
    private string _serviceType;
    public const int SMALL = 2;
    public const int MEDIUM = 8;
    public int NumberOfManuals { get { return _numberOfManuals; } }
    public string ServiceType { get { return _serviceType; } }
    private Dictionary<int, IResult> resultStrategy;

    public Conditional(int numberOfManuals, string serviceType)
    {
        _numberOfManuals = numberOfManuals;
        _serviceType = serviceType;
        resultStrategy = new Dictionary<int, IResult>
        {
              { SMALL, new SmallResult() },
              { MEDIUM, new MediumResult() },
              { MEDIUM + 1, new LargeResult() }
        };
    }

    public int GetHours()
    {
        return resultStrategy.Where(k => _numberOfManuals <= k.Key).First().Value.GetResult(this);
    }
}

public interface IResult
{
    int GetResult(Conditional conditional);
}

public class SmallResult : IResult
{
    public int GetResult(Conditional conditional)
    {
        return conditional.ServiceType.IsWriting() ? WritingResult(conditional) : AnalysisResult(conditional); ;
    }

    private int WritingResult(Conditional conditional)
    {
        return 30 * conditional.NumberOfManuals;
    }

    private int AnalysisResult(Conditional conditional)
    {
        return 10;
    }
}

public class MediumResult : IResult
{
    public int GetResult(Conditional conditional)
    {
        return conditional.ServiceType.IsWriting() ? WritingResult(conditional) : AnalysisResult(conditional); ;
    }

    private int WritingResult(Conditional conditional)
    {
        return (Conditional.SMALL * 30) + (20 * conditional.NumberOfManuals - Conditional.SMALL);

    }

    private int AnalysisResult(Conditional conditional)
    {
        return 20;
    }
}

public class LargeResult : IResult
{
    public int GetResult(Conditional conditional)
    {
        return conditional.ServiceType.IsWriting() ? WritingResult(conditional) : AnalysisResult(conditional); ;
    }

    private int WritingResult(Conditional conditional)
    {
        return (Conditional.SMALL * 30) + (20 * (Conditional.MEDIUM - Conditional.SMALL)) + (10 * conditional.NumberOfManuals - Conditional.MEDIUM);

    }

    private int AnalysisResult(Conditional conditional)
    {
        return 30;
    }
}

public static class ExtensionMethods
{
    public static bool IsWriting(this string value)
    {
        return value == "writing";
    }
}
1
ответ дан 4 December 2019 в 00:23
поделиться

это обычная проблема, есть несколько вариантов, которые я могу придумать. На ум приходят два шаблона проектирования: во-первых, шаблон стратегии и, во-вторых, Factory Pattern . С помощью шаблона стратегии можно инкапсулировать вычисление в объект, например, вы можете инкапсулировать свой метод GetHours в отдельные классы, каждый из которых будет представлять вычисление на основе размера. После того, как мы определили различные стратегии вычислений, мы переносим их в фабрику. Фабрика будет отвечать за выбор стратегии для выполнения вычислений, как и ваш оператор if в методе GetHours. В любом случае взгляните на приведенный ниже код и убедитесь, что вы думаете

В любой момент вы можете создать новую стратегию для выполнения другого вычисления. Стратегия может использоваться разными объектами, что позволяет использовать один и тот же расчет в нескольких местах. Также фабрика может динамически определять, какую стратегию использовать в зависимости от конфигурации, например

class Program
{
    static void Main(string[] args)
    {
        var factory = new HourCalculationStrategyFactory();
        var strategy = factory.CreateStrategy(1, "writing");

        Console.WriteLine(strategy.Calculate());
    }
}

public class HourCalculationStrategy
{
    public const int Small = 2;
    public const int Medium = 8;

    private readonly string _serviceType;
    private readonly int _numberOfManuals;

    public HourCalculationStrategy(int numberOfManuals, string serviceType)
    {
        _serviceType = serviceType;
        _numberOfManuals = numberOfManuals;
    }

    public int Calculate()
    {
        return this.CalculateImplementation(_numberOfManuals, _serviceType);
    }

    protected virtual int CalculateImplementation(int numberOfManuals, string serviceType)
    {
        if (serviceType == "writing")
            return (Small * 30) + (20 * (Medium - Small)) + (10 * numberOfManuals - Medium);
        if (serviceType == "analysis")
            return 30;

        return 0;
    }
}

public class SmallHourCalculationStrategy : HourCalculationStrategy
{
    public SmallHourCalculationStrategy(int numberOfManuals, string serviceType) : base(numberOfManuals, serviceType)
    {
    }

    protected override int CalculateImplementation(int numberOfManuals, string serviceType)
    {
        if (serviceType == "writing")
            return 30 * numberOfManuals;
        if (serviceType == "analysis")
            return 10;

        return 0;
    }
}

public class MediumHourCalculationStrategy : HourCalculationStrategy
{
    public MediumHourCalculationStrategy(int numberOfManuals, string serviceType) : base(numberOfManuals, serviceType)
    {
    }

    protected override int CalculateImplementation(int numberOfManuals, string serviceType)
    {
        if (serviceType == "writing")
            return (Small * 30) + (20 * numberOfManuals - Small);
        if (serviceType == "analysis")
            return 20;

        return 0;
    }
}

public class HourCalculationStrategyFactory
{
    public HourCalculationStrategy CreateStrategy(int numberOfManuals, string serviceType)
    {
        if (numberOfManuals <= HourCalculationStrategy.Small)
        {
            return new SmallHourCalculationStrategy(numberOfManuals, serviceType);
        }

        if (numberOfManuals <= HourCalculationStrategy.Medium)
        {
            return new MediumHourCalculationStrategy(numberOfManuals, serviceType);
        }

        return new HourCalculationStrategy(numberOfManuals, serviceType);
    }
}
1
ответ дан 4 December 2019 в 00:23
поделиться

Вам не нужна Фабрика, если ваши подклассы фильтруют себя по тому, за что они хотят взимать плату. Для этого требуется класс Project для хранения данных, если ничего другого:

class Project {
    TaskType Type { get; set; }
    int? NumberOfHours { get; set; }
}

Поскольку вы хотите легко добавлять новые вычисления, вам нужен интерфейс:

IProjectHours {
    public void SetHours(IEnumerable<Project> projects);
}

И некоторые классы для реализации интерфейса:

class AnalysisProjectHours : IProjectHours {
    public void SetHours(IEnumerable<Project> projects) {
       projects.Where(p => p.Type == TaskType.Analysis)
               .Each(p => p.NumberOfHours += 30);
    }
}

// Non-LINQ equivalent
class AnalysisProjectHours : IProjectHours {
    public void SetHours(IEnumerable<Project> projects) {
       foreach (Project p in projects) {
          if (p.Type == TaskType.Analysis) {
             p.NumberOfHours += 30;
          }
       }
    }
}

class WritingProjectHours : IProjectHours {
    public void SetHours(IEnumerable<Project> projects) {
       projects.Where(p => p.Type == TaskType.Writing)
               .Skip(0).Take(2).Each(p => p.NumberOfHours += 30);
       projects.Where(p => p.Type == TaskType.Writing)
               .Skip(2).Take(6).Each(p => p.NumberOfHours += 20);
       projects.Where(p => p.Type == TaskType.Writing)
               .Skip(8).Each(p => p.NumberOfHours += 10);
    }
}

// Non-LINQ equivalent
class WritingProjectHours : IProjectHours {
    public void SetHours(IEnumerable<Project> projects) {
       int writingProjectsCount = 0;
       foreach (Project p in projects) {
          if (p.Type != TaskType.Writing) {
             continue;
          }
          writingProjectsCount++;
          switch (writingProjectsCount) {
              case 1: case 2:
                p.NumberOfHours += 30;
                break;
              case 3: case 4: case 5: case 6: case 7: case 8:
                p.NumberOfHours += 20;
                break;
              default:
                p.NumberOfHours += 10;
                break;
          }
       }
    }
}

class NewProjectHours : IProjectHours {
    public void SetHours(IEnumerable<Project> projects) {
       projects.Where(p => p.Id == null).Each(p => p.NumberOfHours += 5);
    }
}

// Non-LINQ equivalent
class NewProjectHours : IProjectHours {
    public void SetHours(IEnumerable<Project> projects) {
       foreach (Project p in projects) {
          if (p.Id == null) {
            // Add 5 additional hours to each new project
            p.NumberOfHours += 5; 
          }
       }
    }
}    

Вызывающий код может либо динамически загружать реализаторов IProjectHours (или статически их), а затем просто просматривать список Project через них:

foreach (var h in AssemblyHelper.GetImplementors<IProjectHours>()) {
   h.SetHours(projects);
}
Console.WriteLine(projects.Sum(p => p.NumberOfHours));
// Non-LINQ equivalent
int totalNumberHours = 0;
foreach (Project p in projects) {
   totalNumberOfHours += p.NumberOfHours;
}
Console.WriteLine(totalNumberOfHours);
2
ответ дан 4 December 2019 в 00:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: