Chain of responsiblity and dependency injection
I teach courses about general OO design. One of the classes involves going through all the GoF design patterns.
For the last couple of years, I have a new appreciation for the Chain of responsiblity pattern. As I see it, every middleware based arhitecture is kinda-sorta a Chain of responsiblity, and these have been become very dominant in the industry with the emergence of NodeJs and ASP.NET Core.
So, here's my take on the pattern (coupled with Template method) with a simple example: let's say you have to implement a loan approval process. The lady at the window can approve up until 3000 WhatEvers. If you apply for more, she hands it to the manager, who can approve up until 5000, if you want more, he hands it to the Director. HE can approve anything. They all have different methods for approving the requested loan amount.
public abstract class LoanApprover
{
private readonly LoanApprover nextApprover;
protected LoanApprover(LoanApprover nextApprover)
{
this.nextApprover = nextApprover;
}
protected abstract bool CanHandle(int input);
protected abstract bool Handle(int input);
public bool ApproveLoan(int input)
{
if (CanHandle(input)) { return Handle(input); }
return nextApprover.ApproveLoan(input);
}
}
public class Teller : LoanApprover
{
public Teller() : base(new Manager()) { }
protected override bool CanHandle(int input) => input < 3000;
protected override bool Handle(int input) => input % 2 == 0 && DateTime.Now.DayOfWeek == DayOfWeek.Monday;
}
public class Manager : LoanApprover
{
public Manager() : base(new Director()) { }
protected override bool CanHandle(int input) => input < 6000;
protected override bool Handle(int input) => input % 3 == 0 && DateTime.Now.DayOfWeek == DayOfWeek.Wednesday;
}
public class Director : LoanApprover
{
public Director() : base(null) { }
protected override bool CanHandle(int input) => true;
protected override bool Handle(int input) => input % 7 == 0 && DateTime.Now.DayOfWeek == DayOfWeek.Thursday;
}
Maybe the code could be a bit more up to my standards with a couple of exception throws, but that's not the point.
My point is that one of my students' asked me about how to actually instantiate these classes. We talk about IoC and DI as a principle, but not in detail or not from a technological point of view (i.e. no containers). The actual concern of my student was that by specifying the actual type for the object we pass in to the base constructor every time, we introduce a stronger coupling between the modules.
And that's entirely true. So what can you do about this? Well, here's my idea on how to marry the technology-agnostic design (OO design pattern) with the technology (DI-container) using Autofac.
First, change the constructor of every LoanApprover-descendant like this (i.e. use dependency injection):
public Teller(LoanApprover nextApprover) : base(nextApprover) { }
And then go ahead, and add the Autofac registrations:
ContainerBuilder builder = new ContainerBuilder();
builder.Register(ctx => new Teller(ctx.Resolve<Manager>()));
builder.Register(ctx => new Manager(ctx.Resolve<Director>()));
builder.Register(ctx => new Director(null));
The registration says that when creating a Teller, resolve the Manager, and create the Teller by passing the resolved Manager to the Teller constructor.
For resolving a Manager, resolve the Director and pass that resolved object to the Manager constructor. When creating the Director, just create the Director by passing null to the constructor.
If you build the container and resolve the Teller, you'll have the full chain. And you have no coupling between the elements of the chain (of course, Director should always be the last one in the chain).