Dependency Injection – How to Avoid Temporal Coupling

couplingdependency-injection

Suppose I have the Service that receives dependencies via constructor but also needs to be initialized with custom data (context) before it can be used:

public interface IService
{
    void Initialize(Context context);
    void DoSomething();
    void DoOtherThing();
}

public class Service : IService
{
    private readonly object dependency1;
    private readonly object dependency2;
    private readonly object dependency3;

    public Service(
        object dependency1,
        object dependency2,
        object dependency3)
    {
        this.dependency1 = dependency1 ?? throw new ArgumentNullException(nameof(dependency1));
        this.dependency2 = dependency2 ?? throw new ArgumentNullException(nameof(dependency2));
        this.dependency3 = dependency3 ?? throw new ArgumentNullException(nameof(dependency3));
    }

    public void Initialize(Context context)
    {
        // Initialize state based on context
        // Heavy, long running operation
    }

    public void DoSomething()
    {
        // ...
    }

    public void DoOtherThing()
    {
        // ...
    }
}

public class Context
{
    public int Value1;
    public string Value2;
    public string Value3;
}

Now – the context data is not know beforehand so I cannot register it as a dependency and use DI to inject it into the service

This is how example client looks like:

public class Client
{
    private readonly IService service;

    public Client(IService service)
    {
        this.service = service ?? throw new ArgumentNullException(nameof(service));
    }

    public void OnStartup()
    {
        service.Initialize(new Context
        {
            Value1 = 123,
            Value2 = "my data",
            Value3 = "abcd"
        });
    }

    public void Execute()
    {
        service.DoSomething();
        service.DoOtherThing();
    }
}

As you can see – there are temporal coupling and initialize method code smells involved, because I first need to call service.Initialize to be able to call service.DoSomething and service.DoOtherThing afterwards.

What are the other approaches in which I can eliminate these problems?

Additional clarification of the behavior:

Each instance of the client needs to have it's own instance of the service initialized with client's specific context data. So, that context data is not static or known in advance so it cannot be injected by DI in the constructor.

Best Answer

There are several ways to deal with the initialization problem:

  • As answered in https://softwareengineering.stackexchange.com/a/334994/301401, init() methods are a code smell. Initializing an object is the responsibility of the constructor - that's why we have constructors after all.
  • Add The given service must be initialized to the doc comment of Client constructor and let the constructor throw if the service is not initialized. This moves the responsibility to the one who gives you the IService object.

However, in your example, the Client is the only one that knows the values that are passed to Initialize(). If you want to keep it that way, I'd suggest the following:

  • Add an IServiceFactory and pass it to the Client constructor. Then you can call serviceFactory.createService(new Context(...)) which gives you an initialized IService that can be used by your client.

The factories can be very simple and also allow you to avoid init() methods and use constructors instead:

public interface IServiceFactory
{
    IService createService(Context context);
}

public class ServiceFactory : IServiceFactory
{
    public Service createService(Context context)
    {
        return new Service(context);
    }
}

In the client, OnStartup() is also an initialization method (it just uses a different name). So if possible (if you know the Context data), the factory should directly be called in the Client constructor. If that's not possible, you need to store the IServiceFactory and call it in OnStartup().

When Service has dependencies not provided by Client they would be provided by DI through ServiceFactory:

public interface IServiceFactory
{
    IService createService(Context context);
}    

public class ServiceFactory : IServiceFactory
{        
    private readonly object dependency1;
    private readonly object dependency2;
    private readonly object dependency3;

    public ServiceFactory(object dependency1, object dependency2, object dependency3)
    {
        this.dependency1 = dependency1;
        this.dependency2 = dependency2;
        this.dependency3 = dependency3;
    }

    public Service createService(Context context)
    {
        return new Service(context, dependency1, dependency2, dependency3);
    }
}