I am working on a project that generates technical brochures in batch. The 3rd party API that is being used expects POCOs with property names that match field names used in each of the brochure templates. The task I am seeking advice on is with the data source that will be used to populate these POCOs. The data is based on cascading calculations against a Domain Model. I originally entertained the idea of having the Domain be self-calculating, but there are so many calcs required that it seemed obvious to me this needed to be abstracted away.
I created some hypothetical code that closely mimics the design pattern in question, and I am open to any suggestions or reaffirmations as to whether I am approaching this correctly.
My primary concerns:
The constructor for the "AirplaneResultsContext" class, which injects itself to calculation classes which are also publicly exposed properties. Does this create recursive paths?
The possible occurrance of circular references with cascading calculation calls to various properties/methods in various other classes. Note that I am re-using most of the calculation logic (as was requested) but I can propose the need for it to be refactored if need be.
I noticed I used the word "Intricate", which is usually a telltale warning for code that needs to be abstracted out, but I am struggling to see how I can make this better.
The entity being calculated against. Picture this as an aggregate root for a larger domain model.
class Airplane : Entity<int>
{
public double WingLength { get; set; }
public double FuselageCircumference { get; set; }
public double FuselageLength { get; set; }
public double SeatWidth { get; set; }
public double SeatDepth { get; set; }
public double SeatHeight { get; set; }
public double IsleWidth { get; set; }
public double LegRoomArea { get; set; }
}
The calculation context. The intent is for this object to be passed along as part of a batch process -- an abstract factory which consumes an interface represented by this context class and generates the POCO objects. Note that I also omitted the interfaces and abstract classes that would be used for different calculation strategies for brevity.
class AirplaneResultsContext
{
public AirplaneResultsContext(Airplane airplane)
{
Airplane = airplane;
Fuselage = new FuselageCalculator(this);
Seating = new SeatingCalculator(this);
}
public Airplane Airplane { get; private set; }
public FuselageCalculator Fuselage { get; private set; }
public SeatingCalculator Seating { get; private set; }
public double ComputeWidth()
{
return Airplane.WingLength*2 + Fuselage.ComputeDiameter();
}
}
Calculators. In my non-hypothetical project, these methods are private/protected, cached on the first call, and read back by read-only properties, but I only wanted to show a basic representation of some of the calculation complexity.
class SeatingCalculator
{
private readonly AirplaneResultsContext _context;
public SeatingCalculator(AirplaneResultsContext context)
{
_context = context;
}
public int ComputeNumberOfSeats()
{
double isleArea = _context.Airplane.IsleWidth* _context.Airplane.FuselageLength;
double availableArea = (_context.Fuselage.ComputeDiameter()*_context.ComputeWidth()) - isleArea;
double seatArea = _context.Airplane.SeatDepth * _context.Airplane.SeatWidth + _context.Airplane.LegRoomArea;
return (int) Math.Floor(availableArea/seatArea);
}
}
class FuselageCalculator
{
private readonly AirplaneResultsContext _context;
public FuselageCalculator(AirplaneResultsContext context)
{
_context = context;
}
public double ComputeDiameter()
{
return Math.PI / _context.Airplane.FuselageCircumference;
}
}