3

我有一个问题,我之前尝试过寻求帮助,但当时我无法解决它,所以我现在尝试简化问题,看看我是否可以得到一些更具体的帮助,因为它快把我逼疯了……</p>

基本上,我有这个应用程序的工作(更复杂)版本,它是一个项目成本计算器。但是因为我同时试图学习更好地设计我的应用程序,所以我想就如何改进这个设计提供一些意见。基本上,我想要的主要内容是输入(此处)在两个地方重复出现的条件。之前得到的建议是使用策略模式或者工厂模式。我也知道 Martin Fowler 的书,其中包含使用多态性重构条件的建议。我在他更简单的例子中理解了这个原则。但是我怎么能在这里做这些事情(如果有的话)?在我看来,计算取决于几个条件: 1. 它是一种什么样的服务,写作还是分析?2.项目小吗,中型还是大型?(请注意,可能还有其他参数,同样不同,例如“产品是新的还是以前存在的?”所以应该可以添加这样的参数,但我尽量保持示例简单,只需要两个参数能够得到具体的帮助)

因此,使用多态进行重构将意味着创建许多子类,我已经为第一个条件(服务类型)创建了这些子类,我真的应该为第二个条件(大小)创建更多子类吗?那会变成什么,AnalysisSmall、AnalysisMedium、AnalysisLarge、WritingSmall 等等……???不,我知道这不好,我只是不知道如何使用这种模式?

对于使用策略模式的建议,我基本上看到了同样的问题(我认为工厂模式只是实现上述多态性的帮手)。因此,如果有人对如何以最佳方式设计这些课程有具体建议,我将不胜感激!还请考虑我是否也正确选择了对象,或者是否需要重新设计它们。(像“你应该考虑工厂模式”这样的回应显然不会有帮助......我已经走上了这条路,我对这种情况下的确切方式感到困惑)

问候,

安德斯

代码(非常简化,不要介意我使用字符串而不是枚举,不使用配置文件来存储数据等,一旦我掌握了这些设计问题的窍门,这些代码将在实际应用程序中根据需要完成):

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();
    }
}
4

3 回答 3

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;
    }
}
于 2010-05-05T16:04:16.300 回答
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);
    }
}
于 2010-05-05T12:15:49.400 回答
0

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

这将为您提供 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);
}

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

编辑:再想一想,假设规则可能会改变,在我看来,控制表可能比 OOP 模式更合适。

于 2010-05-05T12:03:33.220 回答