Abstract Factory Pattern comes my mind.
Your taxes change by Continent. So you need ContinentTaxFactory and its depends TaxCalculator
public abstract class ContinentTaxFactory
{
public abstract TaxCalculator CreateTaxCalculator();
}
public abstract class TaxCalculator
{
public abstract double CalculateTax(PieceOfCar pieceOfCar);
}
These two abstract class are your abstraction. And you need TaxRepository to get taxes.
public class TaxRepository
{
public List<ITaxRate> GetIndianTaxes()
{
return new List<ITaxRate>();
}
public List<ITaxRate> GetUSTaxes()
{
return new List<ITaxRate>();
}
}
Rest of implementation is like:
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");
}
}
}
I also define Helper class to determine ContinentTaxFactory. You can do this directly in your code.
After all this implementation, you can call to test from Main.
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);
}
By doing this way (using Abstract Factory Pattern), your question How to get a decoupled design without injecting repositories inside entities is solved I think.
Note: I assume your each piece of car part has own UnitCost and Transportation. Thus, I don't set them.