C# 将条件多态性替换为重构或类似?
我以前试过问这个问题的一个变体。我得到了一些有用的答案,但仍然没有什么对我来说是正确的。在我看来,这应该不是一个很难解决的问题,但我找不到一个优雅简单的解决方案。(这是我之前的帖子,但请先尝试将此处所述的问题视为程序代码,以免受到前面的解释的影响,该解释似乎会导致非常复杂的解决方案:) 基本上,问题是为包含大量服务的项目创建一个计算小时数的计算器。在这种情况下,“写作”和“分析”。对于不同的服务,小时数的计算是不同的:写作是通过“每产品”小时率乘以产品数量来计算的,项目中包含的产品越多,小时率越低,但总小时数是逐步累积的(即,对于中等规模的项目,您同时采用小范围定价,然后将中等范围定价添加到实际产品的数量上)。而对于分析来说,这要简单得多,它只是每个规模范围的批量费率 您如何能够将其重构为一个优雅且最好是简单的面向对象版本(请注意,我绝不会以纯过程的方式编写它,这只是为了用另一种方式简洁地显示问题) 我一直在考虑工厂、战略和装饰模式,但没有一个能很好地发挥作用。(我不久前读过Head First设计模式,描述的decorator和factory模式与这个问题有一些相似之处,但我很难将它们视为上面所述的好的解决方案。decorator的例子似乎非常复杂,只是为了添加调味品,但我不知道它可能在这里工作得更好。至少计算小时数逐渐累积的事实让我想到了装饰者模式…以及《比萨饼工厂》一书中的工厂模式示例…它似乎创造了如此荒谬的类爆炸,至少在他们的示例中是这样。我以前发现工厂模式有很好的用途,但我看不出我是如何合作的我们可以在这里使用它,而不必得到一组非常复杂的类) 如果我要添加一个新参数(比如另一个尺寸,如XSMALL,和/或另一个服务,如“管理”),主要目标是只需在一个地方进行更改(松耦合等)。下面是过程代码示例:C# 将条件多态性替换为重构或类似?,c#,design-patterns,refactoring,conditional,C#,Design Patterns,Refactoring,Conditional,我以前试过问这个问题的一个变体。我得到了一些有用的答案,但仍然没有什么对我来说是正确的。在我看来,这应该不是一个很难解决的问题,但我找不到一个优雅简单的解决方案。(这是我之前的帖子,但请先尝试将此处所述的问题视为程序代码,以免受到前面的解释的影响,该解释似乎会导致非常复杂的解决方案:) 基本上,问题是为包含大量服务的项目创建一个计算小时数的计算器。在这种情况下,“写作”和“分析”。对于不同的服务,小时数的计算是不同的:写作是通过“每产品”小时率乘以产品数量来计算的,项目中包含的产品越多,小时率越
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
}
}
公共类条件
{
私人int_编号手册;
私有字符串_serviceType;
私有常量int SMALL=2;
私人常数int中等=8;
公共时间
{
如果(_numberOfManualsI倾向于以枚举ProjectSize{Small,Medium,Large}开始
和一个简单函数,用于返回给定数量手册的相应枚举。从那里,我将编写不同的ServiceHourCalculator
,WritingServiceHourCalculator
和AnalysisServiceHourCalculator
(因为它们的逻辑完全不同)。每一个都需要一个numberOfManuals,一个ProjectSize,并返回小时数。我可能会创建一个从string到ServiceHourCalculator的映射,因此我可以说:
ProjectSize projectSize = getProjectSize(_numberOfManuals);
int hours = serviceMap.getService(_serviceType).getHours(projectSize, _numberOfManuals);
这样,当我添加一个新的项目大小时,编译器会在每个服务的一些未处理的情况下犹豫不决。它不是在一个地方全部处理的,但在它再次编译之前,它已经全部处理好了,这就是我所需要的
更新
我了解Java,而不是C#(非常熟悉),因此这可能不是100%正确,但创建地图将是这样的:
Map<String, ServiceHourCalculator> serviceMap = new HashMap<String, ServiceHourCalculator>();
serviceMap.put("writing", new WritingServiceHourCalculator());
serviceMap.put("analysis", new AnalysisServiceHourCalculator());
Map serviceMap=newhashmap();
put(“writing”,newwritingServiceHourCalculator());
put(“analysis”,新的AnalysisServiceHourCalculator());
一个好的开始是将条件语句提取到一个方法中(虽然只是一个小方法),并给它一个真正显式的名称。然后将if语句中的逻辑提取到它们自己的方法中,同样使用真正显式的名称。(如果方法名称很长,不用担心,只要它们执行调用的操作即可)
我会用代码写出来,但你最好选择名字
然后,我将讨论更复杂的重构方法和模式。只有当您看到一系列方法调用时,才开始应用模式等
把你的第一个目标定为编写干净、易读和易于理解的代码。很容易对模式感到兴奋(从经验上讲),但如果你不能用抽象的方式描述现有的代码,那么很难应用这些模式
编辑:
因此,为了澄清,你应该让你的if语句看起来像这样
if( isBox() )
{
doBoxAction();
}
else if( isSquirrel() )
{
doSquirrelAction();
}
在我看来,一旦你这样做了,那么应用这里提到的一些模式就更容易了。但是一旦你在你的if语句中仍然有计算器等,那么就更难从树上看到木头,因为你的抽象程度太低了。这是一个常见的问题,我可以考虑一些选项。有两个首先是设计模式,其次是策略模式。使用策略模式可以将计算封装到对象中,例如,您可以将GetHours方法封装到单独的类中,每个类都表示基于大小的计算。一旦我们定义了不同的计算策略,我们就可以工厂将负责选择执行计算的策略,就像GetHours方法中的if语句一样。请查看下面的代码,看看您的想法
在任何时候,您都可以创建一个新的策略来执行不同的计算。该策略可以在不同的对象之间共享,从而允许在多个位置使用相同的计算
Map<String, ServiceHourCalculator> serviceMap = new HashMap<String, ServiceHourCalculator>();
serviceMap.put("writing", new WritingServiceHourCalculator());
serviceMap.put("analysis", new AnalysisServiceHourCalculator());
if( isBox() )
{
doBoxAction();
}
else if( isSquirrel() )
{
doSquirrelAction();
}
class Program
{
static void Main(string[] args)
{
var factory = new HourCalculationStrategyFactory();
var strategy = factory.CreateStrategy(1, "writing");
Console.WriteLine(strategy.Calculate());
}
}
public class HourCalculationStrategy
{
public const int Small = 2;
public const int Medium = 8;
private readonly string _serviceType;
private readonly int _numberOfManuals;
public HourCalculationStrategy(int numberOfManuals, string serviceType)
{
_serviceType = serviceType;
_numberOfManuals = numberOfManuals;
}
public int Calculate()
{
return this.CalculateImplementation(_numberOfManuals, _serviceType);
}
protected virtual int CalculateImplementation(int numberOfManuals, string serviceType)
{
if (serviceType == "writing")
return (Small * 30) + (20 * (Medium - Small)) + (10 * numberOfManuals - Medium);
if (serviceType == "analysis")
return 30;
return 0;
}
}
public class SmallHourCalculationStrategy : HourCalculationStrategy
{
public SmallHourCalculationStrategy(int numberOfManuals, string serviceType) : base(numberOfManuals, serviceType)
{
}
protected override int CalculateImplementation(int numberOfManuals, string serviceType)
{
if (serviceType == "writing")
return 30 * numberOfManuals;
if (serviceType == "analysis")
return 10;
return 0;
}
}
public class MediumHourCalculationStrategy : HourCalculationStrategy
{
public MediumHourCalculationStrategy(int numberOfManuals, string serviceType) : base(numberOfManuals, serviceType)
{
}
protected override int CalculateImplementation(int numberOfManuals, string serviceType)
{
if (serviceType == "writing")
return (Small * 30) + (20 * numberOfManuals - Small);
if (serviceType == "analysis")
return 20;
return 0;
}
}
public class HourCalculationStrategyFactory
{
public HourCalculationStrategy CreateStrategy(int numberOfManuals, string serviceType)
{
if (numberOfManuals <= HourCalculationStrategy.Small)
{
return new SmallHourCalculationStrategy(numberOfManuals, serviceType);
}
if (numberOfManuals <= HourCalculationStrategy.Medium)
{
return new MediumHourCalculationStrategy(numberOfManuals, serviceType);
}
return new HourCalculationStrategy(numberOfManuals, serviceType);
}
}
public class Conditional
{
private int _numberOfManuals;
private string _serviceType;
public const int SMALL = 2;
public const int MEDIUM = 8;
public int NumberOfManuals { get { return _numberOfManuals; } }
public string ServiceType { get { return _serviceType; } }
private Dictionary<int, IResult> resultStrategy;
public Conditional(int numberOfManuals, string serviceType)
{
_numberOfManuals = numberOfManuals;
_serviceType = serviceType;
resultStrategy = new Dictionary<int, IResult>
{
{ SMALL, new SmallResult() },
{ MEDIUM, new MediumResult() },
{ MEDIUM + 1, new LargeResult() }
};
}
public int GetHours()
{
return resultStrategy.Where(k => _numberOfManuals <= k.Key).First().Value.GetResult(this);
}
}
public interface IResult
{
int GetResult(Conditional conditional);
}
public class SmallResult : IResult
{
public int GetResult(Conditional conditional)
{
return conditional.ServiceType.IsWriting() ? WritingResult(conditional) : AnalysisResult(conditional); ;
}
private int WritingResult(Conditional conditional)
{
return 30 * conditional.NumberOfManuals;
}
private int AnalysisResult(Conditional conditional)
{
return 10;
}
}
public class MediumResult : IResult
{
public int GetResult(Conditional conditional)
{
return conditional.ServiceType.IsWriting() ? WritingResult(conditional) : AnalysisResult(conditional); ;
}
private int WritingResult(Conditional conditional)
{
return (Conditional.SMALL * 30) + (20 * conditional.NumberOfManuals - Conditional.SMALL);
}
private int AnalysisResult(Conditional conditional)
{
return 20;
}
}
public class LargeResult : IResult
{
public int GetResult(Conditional conditional)
{
return conditional.ServiceType.IsWriting() ? WritingResult(conditional) : AnalysisResult(conditional); ;
}
private int WritingResult(Conditional conditional)
{
return (Conditional.SMALL * 30) + (20 * (Conditional.MEDIUM - Conditional.SMALL)) + (10 * conditional.NumberOfManuals - Conditional.MEDIUM);
}
private int AnalysisResult(Conditional conditional)
{
return 30;
}
}
public static class ExtensionMethods
{
public static bool IsWriting(this string value)
{
return value == "writing";
}
}
class Project {
TaskType Type { get; set; }
int? NumberOfHours { get; set; }
}
IProjectHours {
public void SetHours(IEnumerable<Project> projects);
}
class AnalysisProjectHours : IProjectHours {
public void SetHours(IEnumerable<Project> projects) {
projects.Where(p => p.Type == TaskType.Analysis)
.Each(p => p.NumberOfHours += 30);
}
}
// Non-LINQ equivalent
class AnalysisProjectHours : IProjectHours {
public void SetHours(IEnumerable<Project> projects) {
foreach (Project p in projects) {
if (p.Type == TaskType.Analysis) {
p.NumberOfHours += 30;
}
}
}
}
class WritingProjectHours : IProjectHours {
public void SetHours(IEnumerable<Project> projects) {
projects.Where(p => p.Type == TaskType.Writing)
.Skip(0).Take(2).Each(p => p.NumberOfHours += 30);
projects.Where(p => p.Type == TaskType.Writing)
.Skip(2).Take(6).Each(p => p.NumberOfHours += 20);
projects.Where(p => p.Type == TaskType.Writing)
.Skip(8).Each(p => p.NumberOfHours += 10);
}
}
// Non-LINQ equivalent
class WritingProjectHours : IProjectHours {
public void SetHours(IEnumerable<Project> projects) {
int writingProjectsCount = 0;
foreach (Project p in projects) {
if (p.Type != TaskType.Writing) {
continue;
}
writingProjectsCount++;
switch (writingProjectsCount) {
case 1: case 2:
p.NumberOfHours += 30;
break;
case 3: case 4: case 5: case 6: case 7: case 8:
p.NumberOfHours += 20;
break;
default:
p.NumberOfHours += 10;
break;
}
}
}
}
class NewProjectHours : IProjectHours {
public void SetHours(IEnumerable<Project> projects) {
projects.Where(p => p.Id == null).Each(p => p.NumberOfHours += 5);
}
}
// Non-LINQ equivalent
class NewProjectHours : IProjectHours {
public void SetHours(IEnumerable<Project> projects) {
foreach (Project p in projects) {
if (p.Id == null) {
// Add 5 additional hours to each new project
p.NumberOfHours += 5;
}
}
}
}
foreach (var h in AssemblyHelper.GetImplementors<IProjectHours>()) {
h.SetHours(projects);
}
Console.WriteLine(projects.Sum(p => p.NumberOfHours));
// Non-LINQ equivalent
int totalNumberHours = 0;
foreach (Project p in projects) {
totalNumberOfHours += p.NumberOfHours;
}
Console.WriteLine(totalNumberOfHours);