4

I am facing the following situation:

I have to develop a system that has to calculate the price of a car so i need to calculate the prices of all the Pieces that make up the car and then total them.

We have two main entities:

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?
   }
}

Depending on the IdOriginCountry, the PieceOfCar Taxes should be calculated in different ways.

For example in USA:

List<TaxUSA> taxesInUSa= TaxesRepository.GetTaxFromUSA(); //different type of objets are used to calculate
double taxUsa = UnitCost* taxesInUSa.TaxTrump + Transportation* taxesInUSa.TaxClinton;

In India:

List<TaxIndia> taxesInIndia = TaxesRepository.GetTaxFromIndia(); //different type of objets are used to calculate
double taxCost = (UnitCost + Transportation)^ taxesInIndia.BadulakeTax ;

As you see, each piece of car, depending on the IdOriginCountry, have different calculations formulas and different requirements of data. USA parts needs some objects, and Indian parts needs others.

How you will solve that problem?

I tried the factory method pattern, but i was forced to inject the TaxesRepository inside the PieceOfCar entities, so i don't think its a good idea. Pieces of car are actually extracted from a repository, so i want all entities without dependencies to any repository.

Example:

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 ;
     }

 }

I would like to get the tax calculation decoupled. What would you suggest?

Factory method will do the job if the parameter for all the GetTaxPrice() were the same for all IdOriginCountries, but I am not that lucky and each country requires specific set of parameters.

Also the info retrieved by the taxesRepositories is an array of objects, it will be nice if it is not retrieved every time I create a PieceOfCar instance. If you need more info please let me know.

3
  • TaxCalculator or TaxCalculationStrategy? Mar 11, 2019 at 19:29
  • @Goyo I see your point....many thanks
    – X.Otano
    Mar 12, 2019 at 8:08
  • You can use Abstract Factory Pattern to overcome this kind of issue. I think its structure is more eligible than the other patterns. I shared complete code as a response.
    – Engineert
    Mar 12, 2019 at 21:16

4 Answers 4

7

Move the entire price calculation into a service. Its the best way of handling this kind of complex logic which is really separate from the car object

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;
    }
}  

You can then use whatever pricing service applies to the specific purchase conditions, Christmas sale, Pre-order, Tax jurisdiction, whatever.

It's important to note that the flaw with injecting a service into the Car or Piece class so that you can have a method Car.CalculateBill() is the Cost calculation is potentially completely independent of anything to do with the Car.

You can easily imagine a tax which is applied based on a combination of Pieces, say 50% or more made in country X. Which would move the calculation to Car.

But you can go further and imagine a tax which applies based on the place of purchase, or method of payment or any one of a dozen factors which you would be hard pressed to lever into your Car object.

Similarly, you are bound to often want to instantiate a Car without first having to know the potential tax rules that would apply should you want to work out the price.

6
  • Ok, thanks sir. I would appreciate if you can explain me why u won't apply strategy pattern as suggested? Also , where u will put that service ? as a domain service?
    – X.Otano
    Mar 11, 2019 at 21:17
  • The strategy pattern is flawed. You will find that for example a single part might not be able to know its tax. It might depend on what other parts are in the car for example. Therefor its impossible to inject a strategy into a Part that can calc the tax correctly
    – Ewan
    Mar 11, 2019 at 21:24
  • Where you keep the service depend on the design of your application. probably at the top layer.
    – Ewan
    Mar 11, 2019 at 21:25
  • there is a problem with the service. If u see your code you are supossing that all the pieces of a car share the same tax calculation procedure, but this is not right. Each piece, inside a car, could have different calculation algorithms
    – X.Otano
    Mar 11, 2019 at 22:23
  • 2
    no its just an example. you can adjust the rules as required. In fact I would pass im the whole Order with the Car and Customer etc etc. But you only have cat and part in uoir example
    – Ewan
    Mar 11, 2019 at 22:24
0

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()
    {
        // get rates from source
        return new List<ITaxRate>();
    }

    public List<ITaxRate> GetUSTaxes()
    {
        // get rates from source
        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.

0

I finally choose to use a Mediator pattern:

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;
        }
    }

Concrete tax calculator:

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;
        }
    }

In my application service:

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);        
     }
}

What do you think? Do you see any disadvantage? Now I got a decoupled code. The only disadvantage could be a run-time error because there is no calculator defined for a specific country.

5
  • you dont show how these are related, if they are, to Car/PieceOfCar
    – Ewan
    Mar 12, 2019 at 15:54
  • @Ewan I fixed the names , sorry
    – X.Otano
    Mar 12, 2019 at 15:58
  • so do you inject the mediator? how is it called
    – Ewan
    Mar 12, 2019 at 15:59
  • @Ewan see my last edit please. Yes it is injected
    – X.Otano
    Mar 12, 2019 at 16:03
  • OK, I would say this fails for the reasons I was saying about Tax potentially being based on the entire Car rather than a sum of pieces and also in that you presumably want to calc the tax for a piece in more than one tax jurisdiction. Here you can only ever calc indian taxes for an indian tax compatible piece. You could end up with a mix of taxes, or if two handlers match you only get the tax from the first one in the list
    – Ewan
    Mar 12, 2019 at 16:08
0

Through Dependency inversion and 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);
    }
}

Note that, Car dictates what's need it to calculate its cost. It does it declaring TaxCalculator and binding method GetCostWithTaxes to the interface.

The contract is established but it leaves open to the TaxCalculator implementations to decide where to go to get the Tax, how to operate with it and PieceOfCar.

At this point, we can implement IndianTaxCalculator and USATaxCalculator somewhere else, making Car and PieceOfCar totally unaware of the TaxRepository and Tax. And agnostic to the maths.

In addition to Ewan's answer, PricingService_Indiaand PricingService_US could be a good place to inject the required calculator

public PricingService_India : IPricingService
{
    private TaxRepository taxRepository;

    public Bill CalculateBill(Car car)
    {
       Bill bill = ...;
       var carCostWithTaxes = car.GetCostWithTaxes(new IndianTaxCalculator(taxRepository));
       return bill;
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.