I need help with the design of an application that I am am writing. In the application employees can book their work within a project (the so called booking object). The booking objects optionally can have budgets. When the budget of a booking object is exhausted then employees must not be able to book on that booking object.
The project leader should set the budget's amount in one of two ways:
- setting a decimal number which represents some monetary amount
- setting the amount through man-days
For example it is legal to say that the booking object X has a budget of 10,000$. It also legal to say that the budget of X consists of 2.5 man-days for a senior, 3 man-days for a junior, and 5.5 man-days for a support. Internally the man-day amount is calculated to a monetary amount (2.5 times price of a senior + 3 times price of junior + 5.5 times price of support). The man-day amount is somehow a calculator but it should be persisted, so not an UI thing only.
The question is how to inject the price(s) into the man-day budget without the man-day budget knowing too much of the booking object. Each booking object is allowed to have differernt prices for the levels ("senior", "junior", "support", etc.).
I came up with following classes.
// could be designed as an interface, too
public abstract class Budget
{
public abstract decimal Amount { get; }
}
Two special classes for the monetary amount and man-day amount.
public class MonetaryBudget : Budget
{
private decimal amount;
public MonetaryBudget(decimal amount)
{
this.amount = amount;
}
}
public class ManDayBudget : Budget
{
private decimal amount;
public ManDayBudget(IEnumerable<ManDay> manDays)
{
this.amount = manDays.Sum(md => md.ManDays * PriceOf(md.Level));
}
}
The consumer of the classes can now write something like this:
var bo_x = new BookingObject();
bo_x.Budget = new MonetaryBudget(10000);
bo_x.Budget = new ManDayBudget(new List<ManDay>
{
new ManDay { ManDays = 2.5, Level = "senior" },
new ManDay { ManDays = 3.0, Level = "junior" },
new ManDay { ManDays = 5.5, Level = "support" }
});
var bo_y = new BookingObject();
bo_y.Budget = new ManDayBudget(new List<ManDay>
{
new ManDay { ManDays = 2.5, Level = "senior" },
new ManDay { ManDays = 3.0, Level = "junior" },
new ManDay { ManDays = 5.5, Level = "support" }
});
// bo_x.Budget.Amount == bo_y.Budget.Amount is not guaranteed to evaluate to true
For cenvenience I have let out the implementation of the Budget.Amount
property in the concrete classes and also the definition of ManDay
.
There will be requirement that other objcets in the application will have a budget, too. It's not crystal clear yet. How should I design my classes so that ManDayBudget
does not know too much about the price finding logic, better does not know anything about booking objects. I have the feeling that I am missing a class which does the calcualtion.
EDIT:
To be clear what the consumer should and can do:
var bo_x = new BookingObject();
bo_x.Budget = new ManDayBudget(new List<ManDay>
{
new ManDay { ManDays = 2.5, Level = "senior" },
new ManDay { ManDays = 3.0, Level = "junior" },
new ManDay { ManDays = 5.5, Level = "support" }
});
var bo_y = new BookingObject();
bo_y.Budget = new ManDayBudget(new List<ManDay>
{
new ManDay { ManDays = 2.5, Level = "senior" },
new ManDay { ManDays = 3.0, Level = "junior" },
new ManDay { ManDays = 5.5, Level = "support" }
});
The monetary amount of the booking object X (bo_x.Budget.Amount
) can be 7.600 and the one of Y can be 9.200 because for each booking object different prices are defined. The consumer says this much man-day budget and nothing more. But the monetary amount must be calculated somehow without too much knowledge about BookingObject in order to reuse the class later.
EDIT 2:
X could have a price set for senior to 100, for junior to 80, etc. and Y could have set price for senior to 125, for junior to 100, etc. Therefore even the man-day amount is set to the same amount of days the monatary amount of the two booking objects differ.
Your question is a bit unclear.
If I understand that you only need to inject the base budget price, looks like the solution can be a bit simple. Please note, this is considered as a decorator pattern though.
EDIT:
Eventhough it is still unclear, I can grasp some of the requirement. Let's say that this is the current design of BookingObject:
With this design, the
Budget.Amount
will obviously relies onBookingObject
'sPrice
for itsBudget.Amount
calculation, making a dependency between each. It is worse, that each derived budget can has different calculation parameter (static monetary, man days, etc), making it harder to implement.Reconstruct the Budget class
The current
Budget
class is troublesome. It has the logic ofAmount
calculation but need thePrice
from other source. You can re-design theBudget
a bit like this, please note you can do the same with interface:You can re-design the input parameter to be another contract like
IPrice
if needed. Then you can re-design theBookingObject
to be as following:The sample of the new
ManDayBudget
andMonetaryBudget
.This design has flaws though. First, now there are 2 ways to retrieve the budget amount.
decimal amount = bo.Amount
decimal amount = bo.Budget.Amount(bo.Price);
Second, the
BookingObject
has dependency to budget class. If the Budget class is constructor injected, then it will making the booking object instantiation harder.