【发布时间】:2010-05-11 19:33:10
【问题描述】:
我之前尝试过问这个问题的变体。我得到了一些有用的答案,但仍然没有什么让我觉得很合适。在我看来,这不应该真的很难破解,但我无法找到一个优雅简单的解决方案。 (这是我之前的帖子,但请先尝试将此处所述的问题视为程序代码,以免受到之前似乎导致非常复杂的解决方案的解释的影响:Design pattern for cost calculator app?)
基本上,问题在于创建一个计算器,用于计算包含许多服务的项目所需的小时数。在这种情况下,“写作”和“分析”。不同服务的工时计算方式不同:写作是通过将“每个产品”的工时率乘以产品的数量来计算的,项目中包含的产品越多,工时率越低,但总数量小时数是逐步累积的(即,对于中型项目,您同时采用小范围定价,然后将中等范围定价添加到实际产品的数量)。而对于分析来说,它要简单得多,它只是每个尺寸范围的批量费率。
您如何能够将其重构为一个优雅且最好是简单的面向对象版本(请注意,我永远不会以纯粹的程序方式编写它,这只是为了以另一种方式简洁地展示问题) .
我一直在考虑工厂、策略和装饰器模式,但无法让任何一个很好地工作。 (我不久前阅读了 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
}
}
感谢所有回复! (但正如我在之前的帖子中所说,我会欣赏实际的代码示例,而不仅仅是“尝试这种模式”,因为正如我所提到的,这就是我遇到的麻烦......)我希望有人有一个非常优雅的解决方案这个问题其实我一开始就觉得很简单……
================================================ =========
新增:
到目前为止,我很欣赏所有的答案,但我仍然没有看到一个真正简单灵活的解决方案(我一开始认为不会很复杂,但显然是)。也可能是我还没有完全正确理解每个答案。但我想我会发布我目前的解决方法(在阅读此处答案的所有不同角度的帮助下)。请告诉我我是否在正确的轨道上。但至少现在感觉它开始变得更加灵活......我可以很容易地添加新参数而无需在很多地方进行更改(我认为!),并且条件逻辑都在一个地方。我在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));
}
}
还有一个调用它的简单示例表单(我想我也可以有一个包装器项目类,其中包含一个包含服务对象的字典,但我还没有做到这一点):
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();
}
}
(我无法包含 xml,因为它在此页面上自动格式化,但它基本上只是每种服务类型的一堆元素,每种服务类型都包含以小时费率作为值的大小。)
我不确定我是否只是将问题推到 xml 文件中(我仍然必须为每个新服务类型添加新元素,并在每个服务类型中添加任何新大小的元素(如果更改)。 ) 但也许不可能实现我想要做的事情,而不必做至少那种类型的改变。使用数据库而不是 xml,更改就像添加一个字段和一行一样简单:
ServiceType 小 中 大
写作 125 100 60
分析 56 104 200
(这里只是格式化为“表格”,虽然列并没有完全对齐......但我在数据库设计方面并不是最好的,也许它应该以不同的方式完成,但你明白了...... .)
请告诉我你的想法!
【问题讨论】:
-
这是一个微小的变化,并没有真正回答任何更广泛的模式问题,但是在“写作”的情况下,您可以递归调用该函数:
return GetHours(SMALL) + 20 * _numberOfManuals - SMALL);当您想添加 XSMALL,可以说读起来更干净。 -
+1 我喜欢人们热衷于编写干净的代码
标签: c# design-patterns refactoring conditional