11

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.

5 Answers 5

18

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);
    }
}
3
  • 1
    Thank you, just like I thought, in the last point... And in the ServiceFactory, would you use the constructor DI in the factory itself for the dependencies needed for the service constructor or the service locator would be more suitable?
    – Dusan
    Commented May 2, 2019 at 12:58
  • 1
    @Dusan don't use Service Locator. If Service has dependencies other than the Context, that would not be provided by the Client, they can be provided via DI to the ServiceFactory to be passed to the Service when createService is called.
    – Mr.Mindor
    Commented May 2, 2019 at 19:53
  • @Dusan If you need to supply different dependencies to different Services (ie: this one needs dependency1_1 but the next one needs dependency1_2), but if this pattern otherwise works for you, then you can use a similar pattern often called a Builder pattern. A Builder allows you to set up an object piecemeal over time if necessary. Then you can do this... ServiceBuilder partial = new ServiceBuilder().dependency1(dependency1_1).dependency2(dependency2_1).dependency3(dependency3_1); and be left with your partially set up service, then later do Service s = partial.context(context).build()
    – Aaron
    Commented May 2, 2019 at 23:00
1

The Initialize method should be removed from the IService interface, as this is an implementation detail. Instead, define another class that takes the concrete instance of Service and calls the initialize method on it. Then this new class implements the IService interface:

public class ContextDependentService : IService
{
    public ContextDependentService(Context context, Service service)
    {
        this.service = service;

        service.Initialize(context);
    }

    // Methods in the IService interface
}

This keeps client code ignorant of the initialization procedure, except where the ContextDependentService class is initialized. You at least limit the parts of your application that need to know about this wonky initialization procedure.

1

It seems to me that you have two options here

  1. Move the Initialisation code to the Context and inject an Initialised Context

eg.

public InitialisedContext Initialise()
  1. Have the first call to Execute call Initialise if its not allready done

eg.

public async Task Execute()
{
     //lock context
     //check context is not initialised
     // init if required
     //execute code...
}
  1. Just throw exceptions if Context isnt initialised when you call Execute. Like SqlConnection.

Injecting a factory is fine if you just want to avoid passing context as a parameter. Say only this particular implementation needs a context and you want not to add it to the Interface

But you essentially have the same problem, what if the factory hasn't got an initialised context yet.

0

You should not depend your interface to any db context and initialize method. You can do it in concrete class constructor.

public interface IService
{
    void DoSomething();
    void DoOtherThing();
}

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

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

        // context is concrete class details not interfaces.
        this.context = context;

        // call init here constructor.
        this.Initialize(context);
    }

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

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

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

And, an answer of your main question would be Property Injection.

public class Service
    {
        public Service(Context context)
        {
            this.context = context;
        }

        private Dependency1 _dependency1;
        public Dependency1 Dependency1
        {
            get
            {
                if (_dependency1 == null)
                    _dependency1 = Container.Resolve<Dependency1>();

                return _dependency1;
            }
        }

        //...
    }

This way you can call all dependencies by Property Injection. But it could be huge number. If so, you can use Constructor Injection for them, but you can set your context by property by checking if it is null.

4
  • OK, great, but... each instance of the client needs to have it's own instance of the service initialized with different context data. That context data is not static or known beforehand so it cannot be injected by DI in the constructor. Then, how do I get/create instance of the service together with other dependencies in my clients?
    – Dusan
    Commented May 2, 2019 at 12:48
  • hmm wont that static constructor run before you set the context? and initialize in the constructor risks exceptions
    – Ewan
    Commented May 2, 2019 at 12:49
  • I am leaning towards injecting factory that can create and initialize the service with the given context data (rather than injecting service itself), but I am not sure if there are better solutions.
    – Dusan
    Commented May 2, 2019 at 12:54
  • @Ewan You are right. I will try to find a solution for it. But before that, I will remove it for now.
    – Engineert
    Commented May 2, 2019 at 12:57
0

Misko Hevery has a very helpful blog post about the case you've faced. You both need newable and injectable for your Service class and this blog post may help you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.