帮助战略模式

时间:2009-04-08 18:23:06

标签: c# design-patterns strategy-pattern

我一直在经历Head First Design Patterns(最近刚刚进入),我正在阅读战略模式,我想到它可能是实施计算税收等常用方法的好方法。我在工作中使用的所有特定对象,但我有一个问题。

这就是我的想法:

public interface ITax
{
    decimal ProvincialTaxRate { get; set; } // Yes, I'm Canadian :)
    decimal CalculateTax(decimal subtotal);
}

public SaskatchewanTax
{
    public decimal ProvincialTaxRate { get; set; }

    public SaskatchewanTax()
    {
        ProvincialTaxRate = new decimal(0.05f);
    }

    public decimal CalculateTax(subtotal)
    {
        return ProvincialTaxRate * subtotal + FederalTaxRate * subtotal;
    }
}

public OntarioTax
{
    public decimal ProvincialTaxRate { get; set; }

    public OntarioTax()
    {
        ProvincialTaxRate = new decimal(0.08f);
    }

    public decimal CalculateTax(decimal subtotal)
    {
        return ProvincialTaxRate * subtotal + FederalTaxRate * subtotal;
    }
}

你可能已经注意到没有FederalTaxRate的声明,这就是我想问的问题。应该去哪儿?

  • 将其传递给每个具体ITax的构造函数似乎是多余的,并允许不正确的行为(所有税务计算器必须共享完全相同的联邦税率)。
  • 同样,创建ITax成员也会使它们不一致。

所有税务计算器是否应该继承静态定义的其他类以及ITax?

public class TaxCalculator
{
    public static decimal FederalTaxRate = new decimal(0.05f);
}

7 个答案:

答案 0 :(得分:22)

我认为这是模式滥用的常见情况。

如果你检查两个“策略”,他们确实完全相同。唯一改变的是ProvincialTaxRate。

我会把事情保持干燥并且不要过度使用这种模式(或任何其他模式),这里你获得了一点灵活性,但是你也有2个类别没有拉动它们的重量,并且可能你不需要那种灵活性。

当您学习新技术或洞察力时,这种情况很常见,您希望在任何地方应用它(它发生在我们每个人身上),即使这样做会损害代码的可读性和可维护性。

我的意见:保持简单

此致

编辑(回应作者对我的回答的评论)

我没有试图取笑你或任何人。这是一个常见的错误,我做了很多次,并且很难学习它,不仅是模式,还有花哨的框架,服务器,新的流行语技术,你可以这么说。

这本书的作者自己警告读者不要过度使用模式,这个答案中的赞成也清楚地表明了一些东西。

但如果由于某种原因你仍然想要实现这种模式,那么这是我的拙见:

  • 为这两个策略创建一个超类,这个超类将是抽象的,并且应该包含其子策略的共享速率值(FederalTaxRate)

  • 在每个子类中继承并实现抽象方法“Calculate”(这里你会看到两种方法都是一样的,但让我们继续)

  • 尝试让每个具体策略不变,总是赞成不变性,正如Joshua Bloch所说。为此,删除ProvincialTaxRate的setter并在其构造函数上指定值或直接在其声明中。

  • 最后,我在StrategySuperclass中创建了一些静态工厂方法,以便您将客户端与实现或具体策略(现在很可能是受保护的类)分离开来。

编辑II: 这是一个带有一些(伪)代码的牧师,使解决方案更加清晰

http://pastie.org/441068

希望有所帮助

此致

答案 1 :(得分:2)

在我看来,您有正确的解决方案 - 创建一个基类,其中包含所有派生类可以继承的加拿大联邦传真速率。静态地定义它是一个非常好的主意。您还可以使FederalTaxRate仅定义税率的访问者函数,以便您可以在运行时从文件或其他内容定义它。

我不认为这是唯一最好的解决方案,但它可以很好地工作。设计模式不应该妨碍你的常识,我认为常识会很好地解决这个问题。

答案 2 :(得分:2)

您可能希望从此代码开始,然后从那里开始:

public interface ITax
{
    decimal CalculateTax(decimal subtotal);
}

public class SaskatchewanTax : ITax
{
    private readonly decimal provincialTaxRate;
    private readonly decimal federalTaxRate;

    public SaskatchewanTax(decimal federalTaxRate)
    {
        provincialTaxRate = 0.05m;
        this.federalTaxRate = federalTaxRate;
    }

    public decimal CalculateTax(decimal subtotal)
    {
        return provincialTaxRate * subtotal + federalTaxRate * subtotal;
    }
}

public class OntarioTax : ITax
{
    private readonly decimal provincialTaxRate;
    private readonly decimal federalTaxRate;

    public OntarioTax(decimal federalTaxRate)
    {
        provincialTaxRate = 0.08m;
        this.federalTaxRate = federalTaxRate;
    }

    public decimal CalculateTax(decimal subtotal)
    {
        return provincialTaxRate * subtotal + federalTaxRate * subtotal;
    }
}

此时,有两个不同的策略对象代表税收计算可能没什么意义,但实际上更实际(我假设税收计算更复杂,而且省份变化更多),这可能有意义。

但是,您应该考虑应用“可能最有效的方法”原则,并且只在您认为需要时才使用策略模式。

答案 3 :(得分:2)

为什么不忘记接口,只使用继承:

public abstract class Tax
{
    protected decimal ProvincialTaxRate; // Yes, you are Canadian ;)
    public decimal CalculateTax(decimal subtotal)
    {
        return ProvincialTaxRate * subtotal + FederalTaxRate * subtotal;
    }
    decimal FederalTaxRate = new decimal(0.20f);
}

public class SaskatchewanTax : Tax
{
    public SaskatchewanTax()
    {
        base.ProvincialTaxRate = new decimal(0.05f);
    }

}

public class OntarioTax : Tax
{
    public OntarioTax()
    {
        base.ProvincialTaxRate = new decimal(0.08f);
    }

}

如果您需要接口,只需在基类中实现它,只需将派生类用于自定义行为/行为。

答案 4 :(得分:1)

几点:

  1. ProvincialTaxRate几乎肯定在接口级别是不可变的(没有set属性)。更改税率似乎不是一个好主意,但这意味着您无法在实施中使用自动属性。

  2. 如果只有一个FederalTaxRate并且它只是一个简单的数值,我认为抽象基类是一种安全的方法。

  3. 不太可取:根据税收的工作方式,你可以说CalculateTax取决于FederalTaxRate因此需要作为参数提供(可能有不同的FederalTaxRates并且你不希望CalculateTax必须知道它们。)

  4. 不要让设计模式的定义妨碍好主意。他们是模式,而不是公式。 ;)


    P.S。我是美国人,所以如果加拿大的税收实际上很简单,我希望国税局明年可以从你的书中拿出一页!

答案 5 :(得分:0)

只是深思熟虑 - 把这个方法放在适当的类中,然后调用它会有什么问题?

public decimal CalculateTax(decimal subtotal, decimal provincialTaxRate, decimal federalTaxRate) {
    return provincialTaxRate * subtotal + federalTaxRate * subtotal;
    }

据我所知,您希望为每个省使用不同的省级费率,但肯定不应该在接口实施中进行硬编码?

答案 6 :(得分:0)

已经给出了很多好的答案。只是为了加我的两分钱。使用这样的设计模式时:

  • 将策略创建为从抽象类派生的单独类
  • 清理事物:将所有重复的代码放在抽象派生基类
  • 中的策略之间

如果您遇到需要州税的税收策略模式有10种情况,而政府税则需要5种策略模式,您可以制作两个派生抽象类(例如StateTax和GovernmentTax),源自主要摘要class(Tax?)和StateTax和GovernmentTax可以派生出具体的类(如OntarioTax,TexasTax等)。如果以后需要更改税收类型,您可以让它从另一个Tax类派生,同时保持所有通用代码不变。