Шаблон дизайна приложения калькулятора стоимости? - PullRequest
3 голосов
/ 05 мая 2010

У меня есть проблема, для которой я пытался получить помощь раньше, но я не смог ее решить тогда, поэтому я пытаюсь упростить проблему сейчас, чтобы посмотреть, смогу ли я получить более конкретную помощь с это потому что это сводит меня с ума ...

По сути, у меня есть рабочая (более сложная) версия этого приложения, которая является калькулятором стоимости проекта. Но поскольку в то же время я пытаюсь научиться лучше проектировать свои приложения, я хотел бы получить некоторую информацию о том, как я могу улучшить этот дизайн. В основном, главное, что я хочу - это вводить условия, которые (здесь) повторяются в двух местах. Предложения, которые я получил раньше, заключались в использовании стратегии или фабричной модели. Я также знаю о книге Мартина Фаулера с предложением Refactor, обусловленным полиморфизмом. Я понимаю этот принцип в его более простом примере. Но как я могу сделать одну из этих вещей здесь (если таковая будет подходящей)? На мой взгляд, расчет зависит от нескольких условий: 1. Что это за услуга, написание или анализ? 2. Проект маленький, средний или большой? (Обратите внимание, что могут быть и другие параметры, одинаково разные, например, «продукты новые или ранее существовали?». Поэтому такие параметры можно добавить, но я постарался сделать пример простым, используя только два параметра. возможность получить конкретную помощь)

То есть рефакторинг с полиморфизмом подразумевал бы создание ряда подклассов, которые у меня уже есть для первого условия (типа обслуживания), и действительно ли мне нужно создавать больше подклассов для второго условия (размера)? Что бы это стало, AnalysisSmall, AnalysisMedium, AnalysisLarge, WritingSmall и т.д… ??? Нет, я знаю, что это нехорошо, я просто не понимаю, как работать с этим шаблоном?

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

С уважением,

Anders

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

public abstract class Service
{
    protected Dictionary<string, int> _hours;
    protected const int SMALL = 2;
    protected const int MEDIUM = 8;

    public int NumberOfProducts { get; set; }
    public abstract int GetHours();
}

public class Writing : Service
{
    public Writing(int numberOfProducts)
    {
        NumberOfProducts = numberOfProducts;
        _hours = new Dictionary<string, int> { { "small", 125 }, { "medium", 100 }, { "large", 60 } };
    }

    public override int GetHours()
    {
        if (NumberOfProducts <= SMALL)
            return _hours["small"] * NumberOfProducts;
        if (NumberOfProducts <= MEDIUM)
            return (_hours["small"] * SMALL) + (_hours["medium"] * (NumberOfProducts - SMALL));
        return (_hours["small"] * SMALL) + (_hours["medium"] * (MEDIUM - SMALL))
            + (_hours["large"] * (NumberOfProducts - MEDIUM));
    }
}

public class Analysis : Service
{
    public Analysis(int numberOfProducts)
    {
        NumberOfProducts = numberOfProducts;
        _hours = new Dictionary<string, int> { { "small", 56 }, { "medium", 104 }, { "large", 200 } };
    }

    public override int GetHours()
    {
        if (NumberOfProducts <= SMALL)
            return _hours["small"];
        if (NumberOfProducts <= MEDIUM)
            return _hours["medium"];
        return _hours["large"];
    }
}

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

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

    private void comboBoxNumberOfProducts_SelectedIndexChanged(object sender, EventArgs e)
    {
        Service writing = new Writing((int) comboBoxNumberOfProducts.SelectedItem);
        Service analysis = new Analysis((int) comboBoxNumberOfProducts.SelectedItem);

        labelWriterHours.Text = writing.GetHours().ToString();
        labelAnalysisHours.Text = analysis.GetHours().ToString();
    }
}

Ответы [ 3 ]

2 голосов
/ 05 мая 2010

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

Если система расчетов фиксирована, то, похоже, шаблон стратегии не подходит. Если это не так ... Почему бы не упростить систему?

Например, выведите базовое количество часов из размера услуги и примените различные скидки или увеличения в зависимости от других настроек.

public class Service
{
    public IServiceSize serviceSize { internal get; set; }
    public IServiceBulkRate serviceBulkRate { internal get; set; }
    public IServiceType serviceType { internal get; set; }
    public int numberOfProducts { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="Service"/> class with default values
    /// </summary>
    public Service()
    {
        serviceSize = new SmallSize();
        serviceBulkRate = new FlatBulkRate();
        serviceType = new WritingService();
        numberOfProducts = 1;
    }

    public decimal CalculateHours()
    {
        decimal hours = serviceSize.GetBaseHours();
        hours = hours * serviceBulkRate.GetMultiplier(numberOfProducts);
        hours = hours * serviceType.GetMultiplier();

        return hours;
    }
}

public interface IServiceSize
{
    int GetBaseHours();
}

public class SmallSize : IServiceSize
{
    public int GetBaseHours()
    {
        return 125;
    }
}

public interface IServiceBulkRate
{
    decimal GetMultiplier(int numberOfProducts);
}

public class FlatBulkRate : IServiceBulkRate
{
    public decimal GetMultiplier(int numberOfProducts)
    {
        return numberOfProducts;
    }
}

public class StaggeredBulkRate : IServiceBulkRate
{
    public decimal GetMultiplier(int numberOfProducts)
    {
        if (numberOfProducts < 2)
            return numberOfProducts;
        else if (numberOfProducts >= 2 & numberOfProducts < 8)
            return numberOfProducts * 0.85m;
        else
            return numberOfProducts * 0.8m;
    }
}

public interface IServiceType
{
    decimal GetMultiplier();
}

public class WritingService : IServiceType
{
    public decimal GetMultiplier()
    {
        return 1.15m;
    }
}
1 голос
/ 05 мая 2010

Я бы переместил логику выбора значения для вычисления в базовый класс Service и делегировал бы фактические вычисления каждому подклассу:

public abstract class Service
{
    private readonly int numberOfProducts;
    private readonly IDictionary<string, int> hours;
    protected const int SMALL = 2; 
    protected const int MEDIUM = 8;

    public Service(int numberOfProducts, IDictionary<string, int> hours)
    {
        this.numberOfProducts = numberOfProducts;
        this.hours = hours;
    }

    public int GetHours()
    {
        if(this.numberOfProducts <= SMALL)
            return this.CalculateSmallHours(this.hours["small"], this.numberOfProducts);
        else if(this.numberOfProducts <= MEDIUM)
            return this.CalculateMediumHours(this.hours["medium"], this.numberOfProducts);
        else
            return this.CalculateLargeHours(this.hours["large"], this.numberOfProducts);
    }

    protected abstract int CalculateSmallHours(int hours, int numberOfProducts);
    protected abstract int CalculateMediumHours(int hours, int numberOfProducts);
    protected abstract int CalculateLargeHours(int hours, int numberOfProducts);
}

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

РЕДАКТИРОВАТЬ: Если вы хотите поддерживать произвольное количество вычислений, вы можете создать класс для управления отображениями между категориями часов и вычисления для каждогоодин.Затем каждый подкласс (или некоторая фабрика) может предоставить соответствующие расчеты для каждой категории:

public class HoursCalculationStrategyCollection
{
    private readonly Dictionary<string, int> hours;

    private readonly Dictionary<string, Func<int, int, int>> strategies;

    public HoursCalculationStrategyCollection(IDictionary<string, int> hours)
    {
        this.hours = hours;
        this.strategies = new Dictionary<string, Func<int, int, int>();
    }

    public void AddCalculationStrategy(string hours, Func<int, int, int> strategy)
    {
        this.strategies[hours] = strategy;
    }

    public int CalculateHours(int numberOfProducts)
    {
        string hoursKey = null;

        if(numberOfProducts <= SMALL)
            hoursKey = small;
        else if(...)
            ...

        Func<int, int, int> strategy = this.strategies[hoursKey];
        return strategy(this.hours[hoursKey], numberOfProducts);
    }
}
0 голосов
/ 05 мая 2010

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

Это даст вам 8 классов: Service, Analysis, Writing, MediumStrategy, SmallStrategy, LargeStrategy и ServiceFactory + интерфейс для стратегий.

ServiceFactory будет тогда содержать код, чтобы решить, какая стратегия будет использоваться. Что-то вроде:

Analysis createAnalysis(int numberOfProducts) {
    SizeStrategy strategy;
    if (numberOfProducts <= SMALL) {
        strategy = new SmallStrategy();
    } else if (numberOfProducts <= MEDIUM) {
        strategy = new MediumStrategy();
    } else {
        strategy = new LargeStrategy();
    }
    return new Analysis(numberOfProducts, strategy);
}

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

EDIT: Если подумать, предположив, что правила могут измениться, мне кажется, что контрольная таблица , вероятно, более подходит, чем шаблоны ООП.

...