替换条件与多态重构或类似? [英] Replace conditional with polymorphism refactoring or similar?

查看:130
本文介绍了替换条件与多态重构或类似?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我以前试图问过这个问题的变体。我得到了一些有用的答案,但仍然没有什么对我来说是对的。在我看来,这不应该是一个难以破解的坚果,但我不能找到一个优雅的简单的解决方案。 (这是我之前的帖子,但是请尝试首先查看这里说明的程序代码,以免受早期解释的影响,这些解释似乎导致了非常复杂的解决方案:成本计算器应用程序的设计模式

I have tried to ask a variant of this question before. I got some helpful answers, but still nothing that felt quite right to me. It seems to me this shouldn't really be that hard a nut to crack, but I'm not able to find an elegant simple solution. (Here's my previous post, but please try to look at the problem stated here as procedural code first so as not to be influenced by the earlier explanation which seemed to lead to very complicated solutions: Design pattern for cost calculator app? )

基本上,问题是为可以包含多个服务的项目创建一个需要几个小时的计算器。在这种情况下,写作和分析。不同服务的时间计算方式不同:通过将每产品小时费率与产品数量相乘计算,并且项目中包含的产品越多,小时费率越低,小时数逐步累积(即中小型项目,您同时承担小范围定价,然后将中等规模定价增加到实际产品数量)。而对于分析来说,它简单得多,它只是每个尺寸范围内的批量速率。

Basically, the problem is to create a calculator for hours needed for projects that can contain a number of services. In this case "writing" and "analysis". The hours are calculated differently for the different services: writing is calculated by multiplying a "per product" hour rate with the number of products, and the more products are included in the project, the lower the hour rate is, but the total number of hours is accumulated progressively (i.e. for a medium-sized project you take both the small range pricing and then add the medium range pricing up to the number of actual products). Whereas for analysis it's much simpler, it is just a bulk rate for each size range.

您将如何将其重构为优雅而优选简单的面向对象版本(请注意,我永远不会以纯粹的程序方式写这个,这只是简洁地以另一种方式显示问题)。

How would you be able to refactor this into an elegant and preferably simple object-oriented version (please note that I would never write it like this in a purely procedural manner, this is just to show the problem in another way succinctly).

我一直在考虑工厂,策略和装饰师的模式,但无法使任何工作顺利。 (我读过头部设计模式,而且描述的装饰器和工厂模式都与这个问题有一些相似之处,但是我很难看到它们是一个很好的解决方案,装饰器的例子似乎很复杂,只是添加调味品,但也许这可以在这里工作得更好,我不知道,至少计算小时数逐渐累积的事实让我想起了装饰图案...以及与比萨工厂相关的工厂模式示例。至少在他们的例子中,我似乎已经很好地使用了工厂模式,但是我看不到我可以在这里使用它,而没有得到一组非常复杂的类)

I have been thinking in terms of factory, strategy and decorator patterns, but can't get any to work well. (I read Head First Design Patterns a while back, and both the decorator and factory patterns described have some similarities to this problem, but I have trouble seeing them as good solutions as stated there. The decorator example seemed very complicated there for just adding condiments, but maybe it could work better here, I don't know. At least the fact that the calculation of hours accumulates progressively made me think of the decorator pattern... And the factory pattern example from the book with the pizza factory...well it just seems to create such a ridiculous explosion of classes, at least in their example. I have found good use for factory patterns before, but I can't see how I could use it here without getting a really complicated set of classes)

主要目标是只需要在一个地方进行更改(松散的耦合等),如果我要添加一个新的参数(如另一个大小,如XSMALL,和/或其他服务,如管理)。这是程序代码示例:

The main goal would be to only have to change in one place (loose coupling etc) if I were to add a new parameter (say another size, like XSMALL, and/or another service, like "Administration"). Here's the procedural code example:

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
    }
}

全部回复赞赏! (但正如我在前面的帖子中所说,我会感谢实际的代码示例,而不仅仅是尝试这种模式,因为正如我所提到的那样,这是我遇到的麻烦...)我希望有一个非常优雅的解决方案对于这个问题,我从一开始就认为真的很简单...

All replies are appreciated! (But as I stated in my previous posts I would appreciate actual code examples rather than just "Try this pattern", because as I mentioned, that is what I'm having trouble with...) I hope someone has a really elegant solution to this problem that I actually thought from the beginning would be really simple...

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

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

新增:

我感谢所有的答案,但我仍然没有看到一个非常简单和灵活的解决方案(一个我以为会起初非常复杂,但显然是)。也可能是我还没有正确地理解每个答案。但是我以为我会把目前的尝试发挥出来(有一些帮助可以从这里的答案中读出所有不同的角度)。请告诉我我是否在正确的轨道上。但至少现在感觉到它开始变得更加灵活了...我可以很容易地添加新的参数,而不必在很多地方改变(我想!),条件逻辑都在一个地方。我有一些在xml中获得基本数据,这简化了部分问题,其中一部分是尝试一种策略类型解决方案。

I appreciate all the answers so far, but I'm still not seeing a really simple and flexible solution to the problem (one I thought wouldn't be very complex at first, but apparently is). It may also be that I haven't quite understood each answer correctly yet. But I thought I'd post my current attempt at working it out (with some help from reading all the different angles in answers here). Please tell me if I'm on the right track or not. But at least now it feels like it's starting to get more flexible... I can quite easily add new parameters without having to change in lots of places (I think!), and the conditional logic is all in one place. I have some of it in xml to get the basic data, which simplifies part of the problem, and part of it is an attempt at a strategy type solution.

这是代码:

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

一个调用它的简单示例窗体(我猜我也可以拥有一个包含服务对象的Dictionary的包装项目类,但是我还没有得到这个):

And a simple example form that calls it (I guess I could also have a wrapper Project class with a Dictionary containing the Service objects, but I haven't gotten to that):

    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,因为它在这个页面上被自动格式化,但它基本上只是一些每个服务类型的元素,每个服务类型包含小时价格的大小。)

(I wasn't able to include the xml because it got automatically formatted on this page, but it's basically just a bunch of elements with each service type, and each service type containing the sizes with the hour rates as values.)

我不知道我是否将问题推送到xml文件(我仍然需要为每个新的servicetype添加新的元素,并在每个servicetype中添加任何新大小的元素,如果改变了)但是也许这是不可能实现我想要做的,而不是至少做这种变化。使用数据库而不是xml,更改将与添加字段和行一样简单:

I'm not sure if I'm just pushing the problem over to the xml file (I'd still have to add new elements for each new servicetype, and add elements for any new size in each servicetype if that changed.) But maybe it's impossible to achieve what I am trying to do and not having to do at least that type of change. Using a database rather than xml the change would be as simple as adding a field and a row:

ServiceType Small Medium Large

ServiceType Small Medium Large

写125 100 60

Writing 125 100 60

分析56 104 200

Analysis 56 104 200

(简单地格式化为表在这里,尽管列不完全一致...我不是数据库设计中最好的,也许应该做的不同,但你得到的想法...)

(Simply formatted as a "table" here, although the columns aren't quite aligned... I'm not the best at database design though, and maybe it should be done differently, but you get the idea...)

请告诉我你的想法!

推荐答案

我会倾向于从枚举开始 ProjectSize {Small,Medium,Large} 和一个简单的函数返回适当的枚举给一个numberOfManuals。从那里,我会写不同的 ServiceHourCalculators WritingServiceHourCalculator AnalysisServiceHourCalculator (因为它们的逻辑有很大的不同)。每个人都会拿到一个NumberOfManuals,一个ProjectSize,并返回小时数。我可能会创建一个从字符串到ServiceHourCalculator的地图,所以我可以说:

I would tend to start with an enumeration ProjectSize {Small, Medium, Large} and a simple function to return the appropriate enum given a numberOfManuals. From there, I would write different ServiceHourCalculators, the WritingServiceHourCalculator and the AnalysisServiceHourCalculator (because their logic is sufficiently different). Each would take a numberOfManuals, a ProjectSize, and return the number of hours. I'd probably create a map from string to ServiceHourCalculator, so I could say:

ProjectSize projectSize = getProjectSize(_numberOfManuals);
int hours = serviceMap.getService(_serviceType).getHours(projectSize, _numberOfManuals);

这样,当我添加了一个新的项目大小时,编译器会阻止每个服务。这并不是在一个地方都被处理,但是在它再次编译之前都会被处理,这就是我需要的。

This way, when I added a new project size, the compiler would balk at some unhandled cases for each service. It's not all handled in one place, but it is all handled before it will compile again, and that's all I need.

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

Update I know Java, not C# (very well), so this may not be 100% right, but creating the map would be something like this:

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

这篇关于替换条件与多态重构或类似?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆