10

我一直在阅读 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);
}
4

7 回答 7

22

我认为这是模式滥用的常见案例。

如果您检查您的两个“策略”,它们会做完全相同的事情。唯一改变的是ProvincialTaxRate。

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

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

我的意见:保持简单

问候

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

我没有试图取笑你或任何人。这是一个常见的错误,我犯了很多次,并且通过艰难的方式学会了它,不仅用模式,而且用花哨的框架、服务器、新的流行语技术,你能想到的。

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

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

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

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

  • 尽量让每一个具体的策略都是不可变的,就像 Joshua Bloch 所说的那样,总是倾向于不可变。为此,删除ProvincialTaxRate 的设置器并在其构造函数中或直接在其声明中指定值。

  • 最后,我将在 StrategySuperclass 中创建一些静态工厂方法,以便您将客户与实现或具体策略分离(现在可以很好地成为受保护的类)

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

http://pastie.org/441068

希望能帮助到你

问候

于 2009-04-08T18:41:03.427 回答
2

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

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

于 2009-04-08T18:34:23.867 回答
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;
    }
}

在这一点上,有两个不同的策略对象代表税收计算可能没有多大意义,但是通过更现实的实现(我假设税收计算更复杂,并且因省而异),这可能是有意义的。

但是,您应该考虑应用“最简单的事情可能会起作用”的原则,并且仅在您认为需要时才使用策略模式。

于 2009-04-08T18:37:37.773 回答
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);
    }

}

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

于 2009-04-08T18:52:10.373 回答
1

几点:

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

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

  3. 不太可取:根据税收的运作方式,您可能会争辩说这CalculateTax取决于FederalTaxRate,因此需要将其作为参数提供(也许有不同的FederalTaxRates,您不想CalculateTax知道它们)。

不要让设计模式的定义妨碍一个好主意。它们是模式,而不是公式。;)


PS我是美国人,所以如果加拿大税真的那么简单,我希望国税局明年能从你的书中吸取一页!

于 2009-04-08T18:35:22.880 回答
0

只是深思熟虑 - 将此方法放在适当的类中并调用它有什么问题?

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

我知道您会希望对每个省使用不同的省率,但肯定不应该在接口实现中硬编码吗?

于 2009-04-08T19:56:51.577 回答
0

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

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

如果您遇到这样一种情况,即您有 10 个需要州税的税收策略模式,以及 5 个使用政府税的策略模式,您可以创建两个派生抽象类(例如 StateTax 和 GovernmentTax),派生自主要抽象类(Tax ?) 并且您可以从 StateTax 和 GovernmentTax 派生具体类(如 OntarioTax、TexasTax 等)。如果以后需要更改税种类型,您可以让它从另一个税类派生,同时保持所有通用代码不变。

于 2016-12-20T13:34:43.740 回答