Rozważmy przez chwilę scenariusz wysyłania wiadomości e-mail. A raczej tą część procesu, w której generowana jest treść. W tagu demo3-finish mamy taki interfejs:
public interface IEmailService { void RegistrationEmail(string email, string link); }
(https://github.com/maniserowicz/di-talk/blob/demo3-finish/src/app/EmailService.cs)
Jego implementacja powinna zająć się dostarczeniem wiadomości w odpowiednie miejsce. Ale czy powinna również babrać się w stringach, razorach czy innych resxach aby skomponować tekst przekazany użytkownikowi końcowemu? Niewydajemiesie, widziałbym raczej coś takiego:
public interface IEmailTemplateGenerator { string ActivationTemplate(string link); }
(https://github.com/maniserowicz/di-talk/blob/demo3-finish/src/app/EmailService.cs)
Dzięki czemu sama czynność wysyłania e-maila będzie prosta i przejrzysta:
public class EmailService : IEmailService { private readonly IEmailTemplateGenerator _templateGenerator; public EmailService(IEmailTemplateGenerator templateGenerator) { _templateGenerator = templateGenerator; } public void RegistrationEmail(string email, string link) { string template = _templateGenerator.ActivationTemplate(link); // send email... } }
(https://github.com/maniserowicz/di-talk/blob/demo3-finish/src/app/EmailService.cs)
Czy powinienem zatem już teraz, od razu, rzucić się do implementacji interfejsu IEmailTemplateGenerator? Nieee, nie chcęęę, to nuuudneeee!!
I na szczęście nie muszę. Zastosowanie interfejsów daje nam wielką zaletę: nie musimy ich implementować! Możemy pisać nasz system inkrementalnie, dodając interfejsy tu i ówdzie, oznaczając miejsca do “zrobienia później”. Całość “się spina”, możemy spokojnie pisać i testować kod mimo, że nie da się go nawet uruchomić. Ale praca idzie do przodu.
Wszystko pięknie i słitaśnie, ale nagle wszystko mi się posypało. O ile w testach UserControllera nie ma problemu, bo interfejs IEmailService mogę sobie zamockować:
public UserController_RegisterUser_Tests() { _emailValidator = Substitute.For<IEmailValidator>(); var linkGenerator = Substitute.For<IActivationLinkGenerator>(); var emailService = Substitute.For<IEmailService>(); _controller = new UsersController(_emailValidator, linkGenerator, emailService); // ...
to już w kodzie “prawdziwym” nie jest tak prosto:
public class WebServer { public void RegisterUser(string email) { var emailValidator = new EmailValidator(); var activationLinkGenerator = new ActivationLinkGenerator(); var emailService = new EmailService(new IEmailTemplateGenerator()); var controller = new UsersController(emailValidator, activationLinkGenerator, emailService); controller.RegisterUser(email); } }
(https://github.com/maniserowicz/di-talk/blob/demo3-finish/src/app/WebServer.cs)
Zwróćcie uwagę na linijkę tworzącą “emailService”. Co my tam mamy? “new IEmailTemplateGenerator()”. Co za kretyn, chce tworzyć instancję interfejsu, przecież to się nawet nie skompiluje! Ano właśnie… Nie mamy implementacji, więc nie mamy jak utworzyć “template generatora”. Więc nie mamy jak utworzyć “email service”. Więc nie mamy jak utworzyć “users controllera”. Więc nie mamy jak obsłużyć żądania. Ba, skoro się nie kompiluje, to nawet nie mamy jak uruchomić testów. Has the shit just hit the fan?
Niekoniecznie. Bo… bo następnym razem zobaczymy jak sobie z tym poradzić.