I'm creating a Tour planner. The Tour
always starts and ends at the same coordinates. Between the start and end, there are Stop
s. Stops contain a specific Sight
to visit during the tour + the time of visit. When adding a Sight to the tour, I insert a new Stop, and recalculate my Stops' arrival time, based on their distances from one another. For this I use the injected ITravelService
instance, in the private recalculateStopTimes(int fromIdx)
method.
My problem is the following: this works until I want to persist a Tour object in the database through an ORM. The functionality will be lost after retrieval, because of the private ITravelService. I thought about injecting the serivce through the InsertSight/RemoveSight/RemoveStop methods, but then I would need to inject it with every public method I create, that modifies Stops. Is there a better way to inject a service like this, to an entity? Or should I even inject it? If not, how could I get the same functionality (tour recalculating it's stops)?
public interface ITravelService
{
public TimeSpan CalculateTimeBetween(Coordinates from, Coordinates to);
}
public class Tour : ITour
{
private readonly List<Stop> _stops;
private ITravelService _travelService;
public IReadOnlyList<Stop> Stops { get { return _stops; } }
public bool IsWithinLimit { get { return _stops.Last().TimeRange.From < (StartTime.TimeOfDay + Length); } }
public Tour(DateTime startTime, TimeSpan length, Coordinates start, ITravelService travelService)
{
StartTime = startTime;
Length = length;
Stop firstStop = new Stop(start, new TimeRange(startTime.TimeOfDay, startTime.TimeOfDay));
Stop lastStop = new Stop(start, new TimeRange(startTime.TimeOfDay, startTime.TimeOfDay));
_stops = new List<Stop>() { firstStop, lastStop };
}
private void recalculateStopTimes(int fromIdx)
{
for (int i = fromIdx; i < _stops.Count - 1; i++)
{
Stop currentStop = _stops[i];
Stop nextStop = _stops[i + 1];
var travelTime = _travelService.CalculateTimeBetween(currentStop.Coordinates, nextStop.Coordinates);
nextStop.Arrival = currentStop.TimeRange.To + travelTime;
}
}
public void InsertSight(Sight sight, int index)
{
if (index == 0 || index == Stops.Count) throw new ArgumentOutOfRangeException("Cannot insert before first, or after last stop.");
_stops.Insert(index, new SightStop(sight, StartTime.DayOfWeek));
recalculateStopTimes(index - 1);
}
public void RemoveSight(Sight sightToRemove)
{
if (_stops.Count == 2) throw new ArgumentException("Sight is not in tour");
int idx = 1;
while (((_stops[idx] as SightStop).Sight != sightToRemove) && idx <= _stops.Count - 1)
{
idx++;
}
if (idx < _stops.Count)
{
RemoveStopAt(idx);
}
else
{
throw new ArgumentException("Sight is not in tour");
}
}
public void RemoveStopAt(int index)
{
if (index > 0 && index < _stops.Count - 1)
{
_stops.RemoveAt(index);
recalculateStopTimes(index - 1);
}
else
{
throw new ArgumentOutOfRangeException("Index was out of range");
}
}
public IReadOnlyList<Sight> SightsInTour
{
get
{
return _stops.Where(stop => stop is SightStop).Select(x => (x as SightStop).Sight).ToList();
}
}
}
If you want to stick with the DDD approach where business logic resides inside the Domain Entity, the answer is to apply Method Injection:
// ITravelService is injected into the public InsertSight method
public void InsertSight(Sight sight, int index, ITravelService travelService)
{
...
}
Method Injection injection is ideal, because constructing objects with both runtime data and dependencies (using Constructor Injection) causes all kinds of trouble. With Method Injection, instead, the consuming class, Tour
, only uses the dependency, but never stores the dependency in any field.
Tips:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With