шаблон стратегии в С#
Я просматриваю шаблоны 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 и т.д.). Если позже потребуется изменение типа налога, вы можете просто позволить ему получить другой класс налогов, сохраняя при этом весь общий код.