C# 将条件多态性替换为重构或类似?

C# 将条件多态性替换为重构或类似?,c#,design-patterns,refactoring,conditional,C#,Design Patterns,Refactoring,Conditional,我以前试过问这个问题的一个变体。我得到了一些有用的答案,但仍然没有什么对我来说是正确的。在我看来,这应该不是一个很难解决的问题,但我找不到一个优雅简单的解决方案。(这是我之前的帖子,但请先尝试将此处所述的问题视为程序代码,以免受到前面的解释的影响,该解释似乎会导致非常复杂的解决方案:) 基本上,问题是为包含大量服务的项目创建一个计算小时数的计算器。在这种情况下,“写作”和“分析”。对于不同的服务,小时数的计算是不同的:写作是通过“每产品”小时率乘以产品数量来计算的,项目中包含的产品越多,小时率越

我以前试过问这个问题的一个变体。我得到了一些有用的答案,但仍然没有什么对我来说是正确的。在我看来,这应该不是一个很难解决的问题,但我找不到一个优雅简单的解决方案。(这是我之前的帖子,但请先尝试将此处所述的问题视为程序代码,以免受到前面的解释的影响,该解释似乎会导致非常复杂的解决方案:)

基本上,问题是为包含大量服务的项目创建一个计算小时数的计算器。在这种情况下,“写作”和“分析”。对于不同的服务,小时数的计算是不同的:写作是通过“每产品”小时率乘以产品数量来计算的,项目中包含的产品越多,小时率越低,但总小时数是逐步累积的(即,对于中等规模的项目,您同时采用小范围定价,然后将中等范围定价添加到实际产品的数量上)。而对于分析来说,这要简单得多,它只是每个规模范围的批量费率

您如何能够将其重构为一个优雅且最好是简单的面向对象版本(请注意,我绝不会以纯过程的方式编写它,这只是为了用另一种方式简洁地显示问题)

我一直在考虑工厂、战略和装饰模式,但没有一个能很好地发挥作用。(我不久前读过Head First设计模式,描述的decorator和factory模式与这个问题有一些相似之处,但我很难将它们视为上面所述的好的解决方案。decorator的例子似乎非常复杂,只是为了添加调味品,但我不知道它可能在这里工作得更好。至少计算小时数逐渐累积的事实让我想到了装饰者模式…以及《比萨饼工厂》一书中的工厂模式示例…它似乎创造了如此荒谬的类爆炸,至少在他们的示例中是这样。我以前发现工厂模式有很好的用途,但我看不出我是如何合作的我们可以在这里使用它,而不必得到一组非常复杂的类)

如果我要添加一个新参数(比如另一个尺寸,如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
    }
}
公共类条件
{
私人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);