设计建议,以避免几个类的变化

时间:2010-04-27 07:36:27

标签: c# design-patterns

我正试图弄清楚如何更优雅地设计一个小应用程序,并使其更能抵抗变化。

基本上它是一种项目价格计算器,问题是有许多参数会影响定价。我试图避免使用每个参数的很多if子句来混乱代码,但我仍然有例如if-clauses在两个地方检查size参数的值。

我有Head First Design Patterns一书,并试图在那里找到想法,但我最接近的是装饰图案,其中有一个例子,starbuzz咖啡设定的价格首先取决于添加的调味品,然后是通过添加尺寸参数(Tall,Grande,Venti)进行锻炼。但这似乎没有帮助,因为添加该参数似乎仍然在许多地方增加了if子句的复杂性(这是一个他们没有进一步解释的练习)。

我要避免的是,如果要更改参数或添加新参数,或者至少在尽可能少的地方进行更改,则必须更改多个类(这里有一些奇特的设计原则,我不知道记得: - ))。

以下是代码。基本上,它计算具有“写入”和“分析”任务的项目的价格,该项目具有尺寸参数和不同的定价模型。稍后还会有其他参数,例如“产品有多新?” (新的,1-5岁,6-10岁)等。对于最佳设计的任何建议都将受到高度赞赏,无论是“设计模式”还是仅仅是良好的面向对象的原则,使其能够抵抗变化(例如添加另一个大小,或更改其中一个大小值,只需要在一个地方而不是在几个if子句中进行更改):

public class Project
{
    private readonly int _numberOfProducts;
    protected Size _size;
    public Task Analysis { get; set; }
    public Task Writing { get; set; }

    public Project(int numberOfProducts)
    {
        _numberOfProducts = numberOfProducts;
        _size = GetSize();
        Analysis = new AnalysisTask(numberOfProducts, _size);
        Writing = new WritingTask(numberOfProducts, _size);

    }

    private Size GetSize()
    {
        if (_numberOfProducts <= 2)
            return Size.small;
        if (_numberOfProducts <= 8)
            return Size.medium;
        return Size.large;
    }
    public double GetPrice()
    {
        return Analysis.GetPrice() + Writing.GetPrice();
    }
}

public abstract class Task
{
    protected readonly int _numberOfProducts;
    protected Size _size;
    protected double _pricePerHour;
    protected Dictionary<Size, int> _hours;
    public abstract int TotalHours { get; }

    public double Price { get; set; }

    protected Task(int numberOfProducts, Size size)
    {
        _numberOfProducts = numberOfProducts;
        _size = size;
    }

    public double GetPrice()
    {
        return _pricePerHour * TotalHours;
    }
}

public class AnalysisTask : Task
{
    public AnalysisTask(int numberOfProducts, Size size)
        : base(numberOfProducts, size)
    {
        _pricePerHour = 850;
        _hours = new Dictionary<Size, int>() { { Size.small, 56 }, { Size.medium, 104 }, { Size.large, 200 } };
    }

    public override int TotalHours
    {
        get { return _hours[_size]; }
    }
}

public class WritingTask : Task
{
    public WritingTask(int numberOfProducts, Size size)
        : base(numberOfProducts, size)
    {
        _pricePerHour = 650;
        _hours = new Dictionary<Size, int>() { { Size.small, 125 }, { Size.medium, 100 }, { Size.large, 60 } };
    }

    public override int TotalHours
    {
        get
        {
            if (_size == Size.small)
                return _hours[_size] * _numberOfProducts;
            if (_size == Size.medium)
                return (_hours[Size.small] * 2) + (_hours[Size.medium] * (_numberOfProducts - 2));
            return (_hours[Size.small] * 2) + (_hours[Size.medium] * (8 - 2)) + (_hours[Size.large] * (_numberOfProducts - 8));
        }
    }
}

public enum Size
{
    small, medium, large
}

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 comboBoxNumberOfProducts_SelectedIndexChanged(object sender, EventArgs e)
    {
        Project project = new Project((int)comboBoxNumberOfProducts.SelectedItem);
        labelPrice.Text = project.GetPrice().ToString();
        labelWriterHours.Text = project.Writing.TotalHours.ToString();
        labelAnalysisHours.Text = project.Analysis.TotalHours.ToString();
    }
}

最后是更改事件中的简单当前调用代码,用于设置大小的组合框...(顺便说一下,我不喜欢我必须使用几个点来到达TotalHours的事实据我所知,这里违反了“最少知识原则”或“德米特定律”,所以对此的投入也会受到赞赏,但这不是问题的要点)

此致

的Anders

4 个答案:

答案 0 :(得分:3)

首先,我认为你应该重新考虑你的设计。项目看起来并不像那样,就我在代码中查看而言,您无法向项目添加更多任务。还要考虑将项目与您计算奖金的方式分开。如果你有不同的计算方法怎么办?这也是责任,很快您的项目可能会增长,并且很难将您计算价格和项目结构的方式分开。通常使用多态来避免使用“if” - 也许您可能希望根据其参数具有不同的项目类型。这可以使用工厂方法来实现,它将获取参数,执行“if”一次,然后创建一些Project子类型,它将知道如何正确计算其奖品。如果您将项目与计算分开,则应考虑使用策略模式来计算奖金。对demeter定律的关注就足够了,因为你暴露了任务。请尝试使用将返回总价格并将委托的方法。原因是,该方法所在的类(项目或计算策略)可以决定如何计算它,它也可以从其他任务中获取信息。如果您计划添加更多任务,则必须调整方法,可以使用带字符串或枚举参数的一个方法来选择计算奖金的具体任务。 BTW。你为什么这么多使用下划线?

答案 1 :(得分:1)

如果您有基于类属性的if ... else语句,请尝试使用策略模式对其进行重构。你可以尝试一本名为“Refactor to Patterns”的书,这是一本关于复原的好书。

答案 2 :(得分:0)

因此,您设计的应用程序具有我所说的设计主体中的一个主要差距:

它假设一个使用数据集。

我的意思是,它假设,只有两个可能的任务,每个都有一个硬编码的价格(在商业世界中根本不存在的东西),并且每个任务计算是“小时”确定性地反对一个恒定的尺寸。我的建议是通过使用数据库来存储新的可能任务/属性/价格/小时比率/大小或通过其他存储方式来配置几乎所有这些,并编写用于管理它的配置表单。

这几乎会立即消除您所暗示的设计问题,即您删除所有硬编码域上下文,而是设置一个配置先例,如果有人不喜欢您的配置方法或希望通过API公开以其他方式使用它。

编辑:我想在下面发表评论但是没有空间:

扩展xml的深度,以有意义地表示更大的数据结构(WritingTask和AnalysisTask)及其组件部分(属性和方法)。这些方法通常可以通过一组规则来定义。您可以对属性和规则进行标记,以便可以独立地进行交互。例如:

<task name="WritingTask">
<property name="numberofproducts" type="int"/>
<property name="Size" type="size">
    <property name="Price" type="decimal">
    <param name="priceperhour" value="650">
    </property>
<property name="hours" type="Dictionary">
    <param name="Size.small" value="125"/> 
    <param name="Size.medium" value="100"/>     
        <param name="Size.large" value="60"/>   
    </property>     
    <method name="TotalHours">
        <rule condition="_size == Size.Small">
    <return value="_hours[_size] * _numberofproducts"/>
        </rule>
        <rule condition="_size == Size.medium">
        <return value="(_hours[Size.small] * 2) + (_hours[Size.medium] * _numberOfProducts - 2))"/>
    </rule>
    <return value="(_hours[Size.small] * 2) + (_hours[Size.medium] * (8 - 2)) + (_hours[Size.large] * (_numberOfProducts - 8))"/> 
    </method> 
</task>

无论如何,对我来说完全尝试这一点已经太晚了,但我明天会和你联系。通过设置结果属性并将配置中的方法与规则相关联,您可以将if的对照保留到数据集中(它应该最清楚)并调整代码以准确地解释此数据。它保持足够的灵活性以处理增长(通过用于数据集创建的sudo语言),但在内部保持不变,而不需要改进更普遍的操作。

答案 3 :(得分:0)

他正在使用下划线作为成员变量。你可以用“我”。或这个。”相反,但它同样清楚。我相信这来自一些旧的java风格标准?我个人非常喜欢它。

相关问题