шаблон стратегии в С#

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

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

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

Ответ 1

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

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

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

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

Мое мнение: держите его простым

Привет

EDIT (В ответ на комментарий автора к моему ответу)

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

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

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

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

  • Наследовать и внедрять абстрактный метод "Рассчитать" в каждом подклассе (здесь вы увидите, что оба метода одинаковы, но пусть продолжаются)

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

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

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

http://pastie.org/441068

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

Привет

Ответ 2

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

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

Ответ 3

Вы можете начать с этого кода и перейти оттуда:

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

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

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

Ответ 4

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

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

}

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

Ответ 5

Несколько точек:

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

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

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

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


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

Ответ 6

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

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

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

Ответ 7

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

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

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