Главная » C# » Реализация налогового движка и налогового счета приложения для вычисления налогов Visual C# (Sharp)

0

Для реализации канадского налогового движка нужен класс, производный от клаа BaseTaxEngine. Это означает, что необходимо реализовать метод CreateTaxAccount (). Кроме этого, нужно создать соответствующее пространство имен, называющееся, скажем, LibTax. Canada. Подробности пространства имен не показываются в коде, т. к. они указываются неявно. Реализация будет выглядеть таким образом:

internal class TaxEngine : BaseTaxEngine {

public override ITaxAccount CreateTaxAccount() { return new TaxAccount();

}

}

В реализации метода CreateTaxAccount () создается экземпляр класса TaxAccount. Это производный класс от класса BaseTaxAccount и поэтому реализует интерфейс ITaxAccount. Реализация класса TaxAccount выглядит таким образом:

internal class TaxAccount : BaseTaxAccount { Province _province;

int _year;

public TaxAccount() {

}

public override double GetTaxRate(double income) { if (_year == 2007) {

if („province == Province.Ontario) { return OntarioTax2007.TaxRate(income);

}

}

throw new NotsupportedException("Year " + _year + " Province " +

„province + " not supported");

}

}

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

уплачивается налог. Метод GetTaxRate о реализует возможность вычисления ногов за 2007 год для провинции Онтарио.

Но здесь у нас имеется проблема с членами данных _province и _уеаг. Эти члены данных используются в вычислениях в  методе GetTaxRate о, но им не присвоены значения.

Присваивание состояния, когда этого не может сделать интерфейс

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

Для иллюстрации проблемы скажем, что метод GetTaxRate о будет содержать ссылку на провинцию и год. Соответствующим образом модифицированный иерфейс ITaxAccount будет выглядеть так:

public interface ITaxAccount {

void AddDeduction(ITaxDeduction deduction); void Addlncome(ITaxIncome income);

double GetTaxRate(double income, Province province, int year);

ITaxDeduction[] Deductions { get; } ITaxIncome[] Income { get; }

}

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

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

Возможным решением данной проблемы может быть переопределение интерфейса следующим образом:

public class Specifics {

public Province CanadianProvince; public State AmericanState;

}

public interface ITaxAccount {

void AddDeduction(ITaxDeduction deduction); void Addlncome(ITaxIncome income);

double GetTaxRate{double income, int year, Specifics specifics); ITaxDeduction[] Deductions { get; }

ITaxIncomeU Income { get; }

}

В данной реализации применяется параметр specifics типа  Specifics. Тип specifics определяет класс, содержащий разнообразную информацию, необходую для определения правильной ставки налога. Но подход с применением класса specifics неправильный по следующим причинам:

•    он должен знать реализацию, что в случае с наследованием является  плохой идеей. Это подобно требованию, чтобы в ресторане обслуживающий нас офицнт был блондином;

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

Таким образом, рассмотренные решения не являются приемлемыми. Кроме этого, у нас все еще остается проблема решения, какую налоговую ставку использовать.

Реализация идей с типом Specifics

Чтобы реализовать решение, начнем с исправления класса TaxAccount. Модифированная  версия  класса  будет содержать  определенный тип  функциональности с членами данных, которые указывают год и провинцию. Исправленная реализация класса  TaxAccount будет выглядеть так:

internal class TaxAccount : BaseTaxAccount { Province „province;

int „year;

public TaxAccount(Province province, int year) {

„province = province; year = year;

}

public override double GetTaxRate(double income) { if („year == 2007) {

if („province == Province.Ontario) { return OntarioTax2007.TaxRate(income);

}

}

throw new NotSupportedException("Year " + „year + " Province " +

„province + " not supported");

}

}

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

Теперь нам нужно исправить класс TaxEngine. Он является ответственным за соание экземпляра TaxAccount, поэтому, для того чтобы создать экземпляр класса TaxAccount для канадской налоговой системы, ему необходимы дополнительные параметры:

internal class TaxEngine : BaseTaxEngine {

public override ITaxAccount CreateTaxAccount() { return new TaxAccount(Province.Ontario, 2007);

}

}

В реализации метода GreateTaxAccount () предполагается, что налоги вычисляются за 2007 год для провинции Онтарио. Данное решение обходит стороной  вопрос, каким образом вычислять налоги для налогоплательщика, например, из Британской Колумбии за 2008 год.

Посмотрев на реализацию класса TaxEngine, мы увидим, что он довольно неболого объема. Это наталкивает на мысль, что можно создать тип TaxEngine для кдой провинции для каждого года. Далее показаны два примера таких классов:

internal class Ontario2007TaxEngine : BaseTaxEngine { public override ITaxAccount CreateTaxAccount() {

return new TaxAccount(Province.Ontario, 2007);

}

}

internal class BritishColumbia2008TaxEngine : BaseTaxEngine { public override ITaxAccount CreateTaxAccount() {

return new TaxAccount(Province.BritishColumbia, 2008);

}

}

Это решение не такое и плохое, т. к. для того чтобы создать экземпляр необходого налогового движка,  нам нужно лишь определить фабрику, которая знает, эемпляр какого класса нужно создать. Но для данной проблемы такое решение бет чрезвычайно трудоемким, т. к. оно может вылиться в сотни, если не тысячи, определений класса TaxEngine. Решение такого типа, со специфическими реализиями, является приемлемым только в случаях с ограниченным числом вариантов, около 10—12.

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

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

Таким образом, правильным решением будет создать новый интерфейс, называийся icanadaTaxEngine. Данный интерфейс добавляет методы фабрики для соания экземпляров типов с параметрами, специфичными для определенной реалации. Определение интерфейса icanadaTaxEngine выглядит таким образом:

public enum Province { Alberta, BritishColumbia, Manitoba, NewBrunswick, Newfoundland Labrador, NovaScotia,

Nunavut,

Ontario, PrinceEdwardlsland, Quebec, Saskatchewan,  Yukon

}

public interface IcanadaTaxEngine {

ITaxAccount CreateTaxAccount(Province province, int year); ITaxIncome CreateCapitalGain(double amount);

}

Определение icanadaTaxEngine содержит два дополнительных метода:

•    метод CreateTaxAccount ()  создает экземпляр налогового счета для определеой провинции и года;

•    метод CreateCapitalGain ()  создает экземпляр  ITaxIncome, пользуясь вычисле-

ниями для канадских капитальных доходов. Реализация TaxEngine становится следующей:

internal class TaxEngine : BaseTaxEngine, IcanadaTaxEngine {

public override ITaxAccount CreateTaxAccount() { return new TaxAccount(Province.Ontario, 2007);

}

public ITaxAccount CreateTaxAccount(Province province, int year) { return new TaxAccount(province, year);

}

public ITaxIncome CreateCapitalGain(double amount) { return new Taxlncame(amount, 0.50);

}

}

В модифицированной реализации класс TaxEngine также является производным от класса BaseTaxEngine, таким образом, удовлетворяя требованиям, предписывающим, чтобы это был налоговый движок общего назначения. А для дополнительных требаний канадской налоговой системы мы реализуем интерфейс icanadaTaxEngine.

Определение интерфейса для конкретной налоговой системы является нормалым, т. к. такой интерфейс не привязан к какой-либо определенной реализации. Чтобы лучше понимать данный метод реализации, конкретный интерфейс можно рассматривать как характеристику, которую  может  поддерживать  реализация. Это следует из примера с фигурами, когда квадрат может поддерживать как интеейс I Square, так И интерфейс IRectangle.

Применение налогового движка

Последним шагом в создании налогового движка будет его применение. Далее приводится пример вычисления налогов за 2007 год для провинции Онтарио.

ITaxEngine engine = EngineCreator.CreateCanadianTaxEngine(); ICanadaTaxEngine canadaEngine = engine as ICanadaTaxEngine; ITaxAccount account = canadaEngine.CreateTaxAccount(Province.Ontario, 2007);

ITaxIncome income = engine.Createlncome(100);

ITaxIncome capitalGain = canadaEngine.CreateCapitalGain(100); account.Addlncome(income);

account.Addlncome(capitalGain);

ITaxDeduction deduction = engine.CreateDeduction(20); account.AddDeduction(deduction);

double taxToPay = engine.CalculateTaxToPay(account);

Console.WriteLine("Tax to pay (" + taxToPay + ")");

Обратите внимание на определение переменных engine и canadaEngine. Это ноально, т. к. мы выбираем характеристику, которая может быть динамически зрошена.

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

  • Комментарии