Помощь с шаблоном стратегии - PullRequest
9 голосов
/ 08 апреля 2009

Я изучал шаблоны проектирования Head First (только что пришел недавно) и читал о шаблоне стратегии, и мне пришло в голову, что это может быть отличным способом реализовать общий способ расчета налогов и т. Д. на все конкретные объекты, которые я использую на работе, но у меня возник вопрос по этому поводу.

Вот что я думал:

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 ]

22 голосов
/ 08 апреля 2009

Я думаю, что это распространенный случай злоупотребления паттернами.

Если вы проверите свои две "стратегии", они делают ТОЧНО одно и то же. Единственное, что меняется, это ProvincialTaxRate.

Я бы оставил вещи СУХИМЫМИ и не злоупотреблял бы этим паттерном (или любым другим), здесь вы получаете немного гибкости, но тогда у вас также есть 2 класса, которые не увеличивают вес, и, вероятно, Вам не нужна эта гибкость.

Это часто встречается, когда вы изучаете новую технологию или проницательность, вы хотите применять ее повсюду (это происходит с каждым из нас), даже если это наносит ущерб читабельности и удобству сопровождения кода.

Мое мнение: будь проще

Привет

РЕДАКТИРОВАТЬ (В ответ на комментарий автора на мой ответ)

Я не пытался подшутить над тобой или кем-либо еще. Это распространенная ошибка, я делал это МНОГИЕ раз и изучал ее нелегко, не только с шаблонами, но и с модными фреймворками, серверами, новыми технологиями модных слов, вы назовете это.

Авторы книги сами предупреждают читателей, чтобы они не злоупотребляли шаблонами, и возражения в этом ответе также явно указывают на что-то.

Но если по какой-то причине вы все еще хотите реализовать шаблон, вот мое скромное мнение:

  • Создание суперкласса для обеих стратегий, этот суперкласс будет абстрактным и должен содержать значение общей ставки их дочерних стратегий (FederalTaxRate)

  • Унаследовать и реализовать абстрактный метод «Calculate» в каждом подклассе (здесь вы увидите, что оба метода одинаковы, но давайте продолжим)

  • Постарайтесь сделать каждую конкретную стратегию неизменной, всегда поддерживайте неизменность, как говорит Джошуа Блох. Для этого удалите сеттер ProvincialTaxRate и укажите значение в его конструкторе или непосредственно в его объявлении.

  • Наконец, я бы создал несколько статических фабричных методов в StrategySuperclass, чтобы вы могли отделить своих клиентов от реализаций или конкретных стратегий (которые теперь могут быть хорошо защищены классами)

Редактировать II: Вот пример с некоторым (псевдо) кодом, чтобы сделать решение немного более понятным

http://pastie.org/441068

Надеюсь, это поможет

Привет

2 голосов
/ 08 апреля 2009

Почему бы вам не забыть об интерфейсах и просто использовать наследование для того, что вы можете:

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

}

Если вам нужен интерфейс, просто реализуйте его в базовом классе и просто используйте производные классы для пользовательского поведения / поведения.

2 голосов
/ 08 апреля 2009

Возможно, вы захотите начать с этого кода и двигаться дальше:

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

На данный момент может не иметь особого смысла иметь два разных объекта стратегии, представляющих расчет налога, но с более реалистичной реализацией (я предполагаю, что расчет налога более сложен и варьируется в зависимости от провинции), это может иметь смысл.

Однако вам следует рассмотреть возможность применения принципа «простейшая вещь, которая могла бы работать», и использовать шаблон стратегии только тогда, когда вы чувствуете, что он необходим.

2 голосов
/ 08 апреля 2009

По моему мнению, у вас есть правильное решение - создать базовый класс, который содержит канадский федеральный тариф на факс, от которого могут наследоваться все ваши производные классы. Статически определять это - прекрасная идея. Вы также можете сделать так, чтобы FederalTaxRate определял только функцию доступа к налоговой ставке, чтобы вы могли определить ее во время выполнения из файла или чего-то еще.

Я не думаю, что это однозначно лучшее решение, но оно будет работать отлично. Шаблоны проектирования не должны мешать вашему здравому смыслу, и я думаю, что здравый смысл прекрасно решит эту проблему.

1 голос
/ 08 апреля 2009

Несколько баллов:

  1. ProvincialTaxRate почти наверняка должен быть неизменным на уровне интерфейса (свойство set отсутствует). Изменение налоговых ставок не кажется хорошей идеей, хотя это означает, что вы не можете использовать автоматические свойства в своей реализации.

  2. Если есть только один FederalTaxRate и это просто простое числовое значение, я думаю, что абстрактный базовый класс является безопасным подходом.

  3. Менее целесообразно: в зависимости от того, как работают налоги, вы можете утверждать, что CalculateTax зависит от FederalTaxRate, и, следовательно, это необходимо указывать в качестве параметра (возможно, существуют другие FederalTaxRates, и хочу CalculateTax знать о них).

Не позволяйте определению шаблона проектирования мешать хорошей идее. Это шаблоны, а не формулы. ;)

<Ч />

P.S. Я американец, поэтому, если канадские налоги на самом деле так просты, я надеюсь, что IRS возьмет страницу из вашей книги в следующем году!

0 голосов
/ 20 декабря 2016

Было дано много хороших ответов. Просто чтобы добавить мои два цента. При использовании шаблона дизайна, подобного этому:

  • Создание стратегий как отдельных классов, производных от абстрактного класса
  • Чтобы очистить вещи: поместите весь дублирующий код между стратегиями в абстрактный производный базовый класс

Если вы когда-нибудь столкнетесь с ситуацией, когда у вас есть 10 шаблонов стратегии для налогов, требующих государственного налога, и 5, использующих государственный налог, вы можете создать два производных абстрактных класса (например, StateTax и GovernmentTax), основанные на основном class (Tax?), а из StateTax и GovernmentTax вы можете получить конкретные классы (например, OntarioTax, TexasTax и т. д.). Если позднее потребуется изменить тип налога, вы можете просто позволить ему наследоваться от другого класса налога, не затрагивая при этом весь общий код.

0 голосов
/ 08 апреля 2009

Просто пища для размышлений - что плохого в том, чтобы поместить этот метод в соответствующий класс и просто вызвать его?

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

Я понимаю, что вы захотите использовать разные провинциальные тарифы для каждой провинции, но, конечно, это не должно быть жестко закодировано в реализации интерфейса?

...