成本计算器应用的设计模式?

时间:2010-05-05 11:48:48

标签: c# design-patterns factory-pattern strategy-pattern

我有一个问题,我之前尝试过帮助,但我当时无法解决问题,所以我现在试图简化问题,看看能不能得到更具体的帮助这是因为它让我发疯了......

基本上,我有一个工作(更复杂)的应用程序版本,这是一个项目成本计算器。但是因为我在尝试学习更好地设计我的应用程序的同时,我想要一些关于如何改进这个设计的意见。基本上我想要的主要是输入条件(这里)在两个地方重复出现。我之前得到的建议是使用策略模式或工厂模式。我也知道Martin Fowler的书,其中建议使用多态来重构条件。我在他更简单的例子中理解这个原则。但是我怎么能在这里做这些事情(如果有的话)?我看待它的方式,计算取决于几个条件: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 个答案:

答案 0 :(得分:2)

在您的计算中,服务类型,服务规模和产品数量之间存在紧密联系,因此将它们分离为模块化块以应用策略模式非常困难。

如果计算系统是固定的,那么似乎策略模式不合适。如果不是......那么,为什么不简化系统?

例如,从服务大小中提取基本小时数,并根据您的其他设置应用各种折扣或增加。

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 :(得分:1)

我将移动逻辑以选择要计算到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);
    }
}

答案 2 :(得分:0)

您可以将工厂和策略模式结合起来。然后,您的工厂将创建一个具体的服务,并通过一个策略来处理不同的大小(小,中或大)。

这将为您提供8个课程:服务,分析,写作,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);
}

在这种情况下,您只能保存很少的代码。作为一项练习,这当然无关紧要,但我认为我不会浪费时间在实践中重构这一点。

编辑: 第二个想法,假设规则可能会改变,在我看来,control table可能比OOP模式更合适。