Skip to main content
added 74 characters in body
Source Link
Engineert
  • 929
  • 1
  • 6
  • 18
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 TaxRepository
{
    public List<ITaxRate> GetIndianTaxes()
    {
        return new List<ITaxRate>();
    }

    public List<ITaxRate> GetUSTaxes()
    {
        return new List<ITaxRate>();
    }        
}
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>();
    }        
}
Source Link
Engineert
  • 929
  • 1
  • 6
  • 18

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.