用多态重构或类似替换条件?

And*_*ers 10 c# refactoring conditional design-patterns

我之前试过问这个问题的变种.我得到了一些有用的答案,但对我来说仍然没有任何感觉.在我看来,这不应该是一个难以破解的坚果,但我无法找到一个优雅的简单解决方案.(这是我以前的帖子,但是请首先尝试将此处所述的问题视为程序代码,以免受早期解释的影响,这些解释似乎会导致非常复杂的解决方案:成本计算器应用程序的设计模式?)

基本上,问题是为可能包含许多服务的项目创建一个计算器.在这种情况下"写作"和"分析".对于不同的服务,小时数的计算方式不同:写作是通过将"每个产品"小时数乘以产品数来计算的,项目中包含的产品越多,小时费率越低,但总数越少.小时数逐步累积(即对于中型项目,您可以采用小范围定价,然后将中等价格定价加到实际产品数量上).然而,分析它更简单,它只是每个尺寸范围的批量率.

你怎么能把它重构成一个优雅的,最好是简单的面向对象的版本(请注意,我绝不会以纯粹的程序方式这样写它,这只是为了以另一种方式简洁地显示问题).

我一直在考虑工厂,战略和装饰模式,但不能让任何工作得很好.(我在前一段时间阅读了Head First Design Patterns,并且所描述的装饰器和工厂模式都与这个问题有一些相似之处,但我很难看到它们是那里所说的好解决方案.装饰器的例子似乎非常复杂,只是添加了调味品,但也许它可以在这里工作得更好,我不知道.至少事实上,小时数的计算逐渐积累,让我想到了装饰模式......以及披萨工厂书中的工厂模式示例.好吧,它似乎只是创造了这样一个荒谬的类爆炸,至少在他们的例子中.我之前已经找到了很好的工厂模式用途,但我看不出如何在没有得到一组非常复杂的类的情况下使用它)

主要目标是只需要在一个地方(疏松耦合等)进行更改,如果我要添加一个新参数(比如另一个大小,如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
    }
}
Run Code Online (Sandbox Code Playgroud)

所有回复都表示赞赏!(但正如我在之前的帖子中所述,我会欣赏实际的代码示例,而不仅仅是"尝试这种模式",因为正如我所提到的,这就是我遇到麻烦...)我希望有人有一个非常优雅的解决方案我从一开始就想到的这个问题真的很简单......

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

新增功能:

到目前为止,我很欣赏所有答案,但我仍然没有看到一个非常简单灵活的问题解决方案(我认为一开始并不是很复杂,但显然是这样).也许我还没有完全理解每个答案.但是我想我会发布我目前的尝试(在这里阅读所有不同角度的答案).如果我在正确的轨道上,请告诉我.但至少现在感觉它开始变得更加灵活......我可以很容易地添加新参数而不必改变很多地方(我想!),并且条件逻辑都在一个地方.我在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<string, decimal> _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));
    }
}
Run Code Online (Sandbox Code Playgroud)

还有一个调用它的简单示例表单(我想我也可以使用包含Service对象的包含的包装器Project类,但我还没有这样做):

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

}
Run Code Online (Sandbox Code Playgroud)

(我无法包含xml,因为它在此页面上自动格式化,但它基本上只是每个服务类型的一堆元素,每个服务类型包含以小时费率为值的大小.)

我不确定我是否只是将问题推到xml文件中(我仍然需要为每个新的servicetype添加新元素,并且如果更改,则在每个servicetype中添加任何新大小的元素.)但是也许我不可能实现我想做的事情而不必至少做那种改变.使用数据库而不是xml,更改就像添加字段和行一样简单:

ServiceType小中大

写作125 100 60

分析56 104 200

(这里只是格式化为"表格",虽然列不完全对齐...虽然我不是最好的数据库设计,也许它应该以不同的方式完成,但你明白了......)

请告诉我你的想法!

Car*_*ter 6

我倾向于从枚举ProjectSize {Small, Medium, Large}和一个简单的函数开始,给出一个numberOfManuals返回适当的枚举.从那里,我会写不同的ServiceHourCalculators,WritingServiceHourCalculatorAnalysisServiceHourCalculator(因为他们的逻辑是完全不同的).每个人都需要一个numberOfManuals,一个ProjectSize,并返回小时数.我可能会创建一个从字符串到ServiceHourCalculator的映射,所以我可以说:

ProjectSize projectSize = getProjectSize(_numberOfManuals);
int hours = serviceMap.getService(_serviceType).getHours(projectSize, _numberOfManuals);
Run Code Online (Sandbox Code Playgroud)

这样,当我添加一个新的项目大小时,编译器会对每个服务的一些未处理的情况保持警惕.它不是全部都在一个地方处理,但是它会在再次编译之前处理完毕,这就是我所需要的.

更新 我知道Java,而不是C#(非常好),所以这可能不是100%正确,但创建地图将是这样的:

Map<String, ServiceHourCalculator> serviceMap = new HashMap<String, ServiceHourCalculator>();
serviceMap.put("writing", new WritingServiceHourCalculator());
serviceMap.put("analysis", new AnalysisServiceHourCalculator());
Run Code Online (Sandbox Code Playgroud)