次の状況に直面しています。
車の価格を計算するシステムを開発する必要があるので、車を構成するすべてのピースの価格を計算し、それらを合計する必要があります。
2つの主要なエンティティがあります。
public class Car
{
public int Id{get;}
public List<PieceOfCar> Pieces{get;}
}
public abstract class PieceOfCar
{
public double UnitCost{get;}
public double Transportation{get;}
public string IdOriginCountry{get;}
public abstract double GetTaxPrice();
{
//how do i design this part?
}
}
IdOriginCountryに応じて、PieceOfCar税はさまざまな方法で計算する必要があります。
たとえば米国では:
List<TaxUSA> taxesInUSa= TaxesRepository.GetTaxFromUSA(); //different type of objets are used to calculate
double taxUsa = UnitCost* taxesInUSa.TaxTrump + Transportation* taxesInUSa.TaxClinton;
インドで:
List<TaxIndia> taxesInIndia = TaxesRepository.GetTaxFromIndia(); //different type of objets are used to calculate
double taxCost = (UnitCost + Transportation)^ taxesInIndia.BadulakeTax ;
ご覧のように、IdOriginCountryに応じて、車の各部分には異なる計算式とデータの異なる要件があります。アメリカの部品にはいくつかのオブジェクトが必要で、インドの部品には他のオブジェクトが必要です。
どのようにその問題を解決しますか?
私はファクトリー・メソッド・パターンを試しましたが、PieceOfCarエンティティー内にTaxesRepositoryを注入することを余儀なくされたので、それは良い考えではないと思います。車の断片は実際にはリポジトリーから抽出されるため、どのリポジトリーにも依存しないすべてのエンティティーが必要です。
例:
public IndiaPieceOfCar:PieceOfCar()
{
public IndiaPieceOfCar(TaxRepository taxesRepo)
{//TaxRepository injected}
public override double GetTaxPrice();
{
var taxesIndia= TaxesRepository.GetTaxFromIndia(); //different type of objets are used to calculate
return (UnitCost + Transportation)^ taxesIndia.BadulakeTax ;
}
}
税計算を切り離してもらいたいのですが。あなたは何を提案しますか?
すべてのGetTaxPrice()のパラメーターがすべてのIdOriginCountriesで同じである場合、ファクトリーメソッドはその仕事をしますが、私はそれほど幸運ではなく、各国には特定のパラメーターセットが必要です。
また、taxRepositoriesによって取得される情報はオブジェクトの配列です。PieceOfCarインスタンスを作成するたびに取得されない場合は、Niceになります。さらに情報が必要な場合はお知らせください。
価格計算全体をサービスに移動します。車のオブジェクトから実際に分離されているこの種の複雑なロジックを処理する最良の方法
public interface IPricingService
{
public Bill CalculateBill(Car car);
}
public PricingService_US : IPricingService
{
public Bill CalculateBill(Car car)
{
foreach(var p in car.Pieces)
{
//get prices for the US
//get tax rules for the US
}
//add special deals available in US by state etc
return bill;
}
}
public PricingService_India : IPricingService
{
public Bill CalculateBill(Car car)
{
foreach(var p in car.Pieces)
{
//get prices for India
//get tax rules for India
}
//add special discount
//add shipping cost
//whatever custom logic
return bill;
}
}
その後、特定の購入条件、クリスマスセール、プレオーダー、税管轄などに適用される価格サービスを使用できます。
Car.CalculateBill()メソッドを使用できるように、サービスをCarクラスまたはPieceクラスに注入する際の欠陥は、コストの計算がCarとの関係から完全に独立している可能性があることに注意することが重要です。
ピースの組み合わせに基づいて適用される税金、たとえば国Xで作成された50%以上の税金を簡単に想像できます。これにより、計算が車に移動します。
しかし、さらに進んで、購入場所、支払い方法、またはCarオブジェクトに押し込むのが難しい12の要素のいずれかに基づいて適用される税金を想像できます。
同様に、価格を計算したい場合に適用される可能性のある潜在的な税法を最初に知る必要なく、Carをインスタンス化したいことがよくあります。
Dependency inversionおよびStrategy patternを介して
public class PieceOfCar
{
public double UnitCost{get;}
public double Transportation{get;}
public string IdOriginCountry{get;}
}
public class Car
{
public int Id{get;}
public List<PieceOfCar> Pieces{get;}
public double GetCostWithTaxes(TaxCalculator calculator);
{
var taxes = 0.0;
foreach(piece in Pieces){
taxes += calculator.calc(piece);
}
return taxes;
}
public interface TaxCalculator{
double calc(PieceOfCar piece);
}
}
Car
は、コストを計算するために何が必要かを決定することに注意してください。 TaxCalculator
を宣言し、メソッドにGetCostWithTaxes
をインターフェースにバインドします。
コントラクトは確立されていますが、TaxCalculator
の実装、Tax
の取得先、操作方法、およびPieceOfCar
を決定するためのオープンなままです。
この時点で、IndianTaxCalculator
とUSATaxCalculator
をどこかに実装して、Car
とPieceOfCar
がTaxRepository
とTax
をまったく認識しないようにすることができます。そして、数学にとらわれない。
ユアンの答えに加えて、PricingService_India
およびPricingService_US
は、必要な計算機を挿入するのに適した場所かもしれません
public PricingService_India : IPricingService
{
private TaxRepository taxRepository;
public Bill CalculateBill(Car car)
{
Bill bill = ...;
var carCostWithTaxes = car.GetCostWithTaxes(new IndianTaxCalculator(taxRepository));
return bill;
}
}
抽象的なファクトリパターン が頭に浮かびます。
大陸ごとに税金が変わります。したがって、ContinentTaxFactory
が必要であり、TaxCalculator
に依存します
public abstract class ContinentTaxFactory
{
public abstract TaxCalculator CreateTaxCalculator();
}
public abstract class TaxCalculator
{
public abstract double CalculateTax(PieceOfCar pieceOfCar);
}
これらの2つの抽象クラスは、抽象化です。そして、税金を得るためにTaxRepository
が必要です。
public class TaxRepository
{
public List<ITaxRate> GetIndianTaxes()
{
// get rates from source
return new List<ITaxRate>();
}
public List<ITaxRate> GetUSTaxes()
{
// get rates from source
return new List<ITaxRate>();
}
}
残りの実装は次のようなものです:
public class IndianTaxFactory : ContinentTaxFactory
{
private readonly TaxRepository _taxRepository;
public IndianTaxFactory(TaxRepository taxRepository)
{
_taxRepository = taxRepository;
}
public override TaxCalculator CreateTaxCalculator()
{
return new IndianTaxCalculator(_taxRepository.GetIndianTaxes());
}
}
public class USTaxFactory : ContinentTaxFactory
{
private readonly TaxRepository _taxRepository;
public USTaxFactory(TaxRepository taxRepository)
{
_taxRepository = taxRepository;
}
public override TaxCalculator CreateTaxCalculator()
{
return new UsTaxCalculator(_taxRepository.GetUSTaxes());
}
}
public class IndianTaxCalculator : TaxCalculator
{
private const string Badulake_Tax_Rate = "Badulake";
private List<ITaxRate> _taxRates;
public IndianTaxCalculator(List<ITaxRate> taxRates)
{
_taxRates = taxRates;
}
public override double CalculateTax(PieceOfCar pieceOfCar)
{
double taxRate = _taxRates.Single(t => t.Type == Badulake_Tax_Rate).Rate;
return (pieceOfCar.UnitCost + pieceOfCar.Transportation) * (1 + taxRate);
}
}
public class UsTaxCalculator : TaxCalculator
{
private const string Clinton_TAX_RATE = "Clinton";
private const string Trump_TAX_RATE = "Trump";
private List<ITaxRate> _taxRates;
public UsTaxCalculator(List<ITaxRate> taxRates)
{
_taxRates = taxRates;
}
public override double CalculateTax(PieceOfCar pieceOfCar)
{
double totalTax = 0.0;
totalTax += TransportationTax(pieceOfCar.Transportation);
totalTax += UnitPriceTax(pieceOfCar.UnitCost);
return totalTax;
}
private double TransportationTax(double transportationPrice)
{
double taxRate = _taxRates.Single(t => t.Type == Clinton_TAX_RATE).Rate;
return calculateTax(transportationPrice, taxRate);
}
private double UnitPriceTax(double unitPrice)
{
double taxRate = _taxRates.Single(t => t.Type == Trump_TAX_RATE).Rate;
return calculateTax(unitPrice, taxRate);
}
private double calculateTax(double price, double rate)
{
return price * (1 + rate);
}
}
public class Car
{
public int Id { get; }
public List<PieceOfCar> Pieces { get; set; }
}
public abstract class PieceOfCar
{
public double UnitCost { get; }
public double Transportation { get; }
public string IdOriginCountry { get; }
public double GetTaxPrice()
{
ContinentTaxFactory continentTaxFactory = Helper.DetermineContinentTaxFactory(this.IdOriginCountry);
return continentTaxFactory.CreateTaxCalculator().CalculateTax(this);
}
}
public interface ITaxRate
{
string Type { get; }
double Rate { get; }
}
public class Wheel : PieceOfCar
{
// implementation
}
public class Engine : PieceOfCar
{
// implementation
}
public class Bill
{
private Car _car;
private readonly double _totalPrice = 0.0 ;
public double TotalPrice {
get
{
return _totalPrice;
}
}
public Bill(Car car)
{
_car = car;
_totalPrice = CalculateBill();
}
private double CalculateBill()
{
double bill = 0.0;
if (_car != null && _car.Pieces.Count > 0)
{
foreach (var pieceOfCar in _car.Pieces)
{
double pieceTotalPrice = 0.0;
pieceTotalPrice = pieceOfCar.UnitCost + pieceOfCar.Transportation + pieceOfCar.GetTaxPrice();
bill += pieceTotalPrice;
}
}
return bill;
}
}
public static class Helper
{
public static ContinentTaxFactory DetermineContinentTaxFactory(string idOriginCountry)
{
TaxRepository taxRepository = new TaxRepository();
switch (idOriginCountry)
{
case "US":
return new USTaxFactory(taxRepository);
case "IND":
return new IndianTaxFactory(taxRepository);
default:
throw new Exception("Continent Not Found");
}
}
}
また、Helper
クラスを定義してContinentTaxFactory
を決定します。これは、コードで直接行うことができます。
このすべての実装の後、Main
からtestを呼び出すことができます。
public static void Main(string[] args)
{
Car car = new Car();
car.Pieces = new List<PieceOfCar>
{
new Wheel(),
new Engine()
};
Bill carBill = new Bill(car);
System.Console.WriteLine(carBill.TotalPrice);
}
このようにすることで( Abstract Factory Pattern を使用)、あなたの質問エンティティ内にリポジトリを挿入せずに分離された設計を取得する方法は解決されると思います。
注:私はあなたの車の各部分にUnitCost
とTransportation
があると思います。したがって、私はそれらを設定しません。
最後に、メディエーターパターンの使用を選択します。
public interface ITaxCalculatorHandler
{
bool IsCompatible(ItemCotization itemCotiz);
double Calculate(ItemCotization itemCotiz);
}
public class TaxCalculatorMediator
{
IEnumerable<ITaxCalculatorHandler> TaxCalculatorHandlers { get; }
readonly ITaxesRepository taxesRepository;
public TaxCalculatorMediator(ITaxesRepository taxesRepository)
{
var taxesAwsa = taxesRepository.GetAwsaAll();
var taxesAwbra= taxesRepository.GetAwbraAll();
var taxesAwind = taxesRepository.GetAwindAll();
var taxCalculatorHandlers = new List<ITaxCalculatorHandler>
{
new EuropeTaxCalculatorHandler(taxesAwsa),
new IndiaTaxCalculatorHandler(taxesAwbra),
new UsaTaxCalculatorHandler(taxesAwind)
};
TaxCalculatorHandlers = taxCalculatorHandlers;
}
public double PerformCalculations(PieceOfCar pieceOfCar)
{
foreach(ITaxCalculatorHandler handler in TaxCalculatorHandlers)
{
if(handler.IsCompatible(pieceOfCar))
{
return handler.Calculate(pieceOfCar);
}
}
return 0;
}
}
具体的な税計算機:
public class EuropeTaxCalculatorHandler:ITaxCalculatorHandler
{
readonly List<EuropeTax> _masterData;
public EuropeTaxCalculatorHandler(List<EuropeTax> masterDataTax)
{
_masterData = masterDataTax;
}
public bool IsCompatible(PieceOfCar pieceOfCar)
{
return pieceOfCar.IdAssemblyFactory == 1;
}
public double Calculate(PieceOfCar pieceOfCar)
{
var taxes = _masterData.FirstOrDefault(x => x.TaxCode.Equals(itemCotiz.TaxCode));
var totaltax= ((itemCotiz.UnitCost + itemCotiz.TransportationCost) * taxes.Tax1/100.0);
return totaltax;
}
}
私のアプリケーションサービス:
public class CostCalculationsService
{
readonly ICarsRepository _carsRepository;
readonly ITaxesRepository _taxesRepository;
readonly TaxCalculatorMediator _taxCalculatorMediator;
public CostCalculationsService(ICarsRepository carsRepository,
ITaxesRepository taxesRepository, TaxCalculatorMediator taxCalculatorMediator)
{
_carsRepository= carsRepository;
_taxesRepository = taxesRepository;
_taxCalculatorMediator = taxCalculatorMediator;
}
public void UpdateCostOfCar(int idCar)
{
var car=_carRepository.GetCarById(idCar);
foreach (var pieceOfCar in car.PiecesOfCar)
double taxCalculation= _taxCalculatorMediator.PerformCalculations(pieceOfCar);
}
}
どう思いますか?何かデメリットはありますか?今、分離したコードを取得しました。特定の国に定義された計算機がないため、唯一の欠点は実行時エラーである可能性があります。