Any time you instantiate or reference a concrete type within a class, you take a Dependency and make your code harder to test.
public Licence(Instant expirey, IClock clock)
{
//variables or local variables
this.expirey = expirey;
this.clock = clock;
}
we are injecting a dependency here. More loosely bound here than
public Licence(Instant expirey)
{
//variables or local variables
this.expirey = expirey;
this.clock = SystemClock.Instance;
}
tightly bound to a concrete implementation here.
We made another class here called Diary which uses a clock too:
//calendaring application which will want to know what time it is too!
class Diary
{
private readonly LocalDatePattern outputPattern = LocalDatePattern.CreateWithInvariantInfo("yyyy-MM-dd");
//fields
private readonly IClock clock;
private readonly CalendarSystem calendar;
private readonly DateTimeZone timeZone;
//by demanding the dependencies in the constructor, classes are more flexible
//especially if they are interfacess
public Diary(IClock clock, CalendarSystem calendar, DateTimeZone timeZone)
{
this.clock = clock;
this.calendar = calendar;
this.timeZone = timeZone;
}
public string FormatToday()
{
//in system default timezone, and system default calendar
//DateTime dateTime = DateTime.Today;
LocalDate date = clock.Now.InZone(timeZone, calendar).LocalDateTime.Date;
if (date.Month == 4)
{ //ncrunch showing black here - so tests are not covering this codepath
return "April fool";
}
return outputPattern.Format(date);
}
}
//still a class lib so can't run it.. demonstrates concept
public class Program
{
static void Main()
{
//manual dependency injection - this takes a lot of time and code to do
//so people invented dependency injection frameworks aka Inversion of Control.
IClock clock = SystemClock.Instance;
Licence licence = new Licence(Instant.UnixEpoch, clock);
Diary diary = new Diary(clock,CalendarSystem.Iso, DateTimeZone.GetSystemDefault());
DiaryPresenter presenter = new DiaryPresenter(diary, licence);
}
}
class DiaryPresenter
{
public DiaryPresenter(Diary diary, Licence licence)
{
}
}
class Licence
{
private readonly Instant expirey;
private readonly IClock clock;
public Licence(Instant expirey, IClock clock)
{
//variables or local variables
this.expirey = expirey;
this.clock = clock;
}
//property
public bool HasExpired
{
get { return clock.Now >= expirey; }
}
}
//calendaring application which will want to know what time it is too!
class Diary
{
private readonly LocalDatePattern outputPattern = LocalDatePattern.CreateWithInvariantInfo("yyyy-MM-dd");
//fields
private readonly IClock clock;
private readonly CalendarSystem calendar;
private readonly DateTimeZone timeZone;
//by demanding the dependencies in the constructor, classes are more flexible
//especially if they are interfacess
public Diary(IClock clock, CalendarSystem calendar, DateTimeZone timeZone)
{
this.clock = clock;
this.calendar = calendar;
this.timeZone = timeZone;
}
public string FormatToday()
{
//in system default timezone, and system default calendar
//DateTime dateTime = DateTime.Today;
LocalDate date = clock.Now.InZone(timeZone, calendar).LocalDateTime.Date;
if (date.Month == 4)
{ //ncrunch showing black here - so tests are not covering this codepath
return "April fool";
}
return outputPattern.Format(date);
}
}
Manually doing dependency injection. Setting up each component explicitly. Each is loosely coupled
Licences don’t know about Diaries
Diaries don’t know about Licences..
DiaryPresenter knows about diary and licence, but not about a clock or timezone directly.
notice SystemClock.Instance is a singleton.
can get very hard to maintain