1

I have this (fictional) desktop application which allows project managers to search for idle developers and assign tasks. The model is:

interface IDeveloperFactory
{
   IDeveloper CreateDeveloper(string name);
}

interface IDeveloper 
{
    void WriteCode();
    void WriteUnitTest();
}

interface ITask
{
    void Complete();
}

class Developer : IDeveloper
{
   public Developer(string name) { ... }

   public void WriteCode()
   {
       Console.WriteLine("Developer {0} is writing code... done", name);
   }
   public void WriteUnitTest()
   {
       Console.WriteLine("Developer {0} is writing unit test... done", name);
   }
}

class WriteCodeTask : ITask
{
   public Task(Lazy<IDeveloper> lazyDeveloper) { ... }

   public void Complete()
   {
       var dev = lazyDeveloper.Value;
       dev.WriteCode();
   }
}

Developer has a runtime dependency so I will use DeveloperFooFactory (which is configured as typed factory) to get IDeveloper's. While a IDeveloper is in use I also need ITask's which use the developer instance and my work would be much easier if I could just ask the container to resolve them:

var developerFactory = container.Resolve<IDeveloperFactory>();
var dev1 = developerFactory.CreateDeveloper("dev 1");
var task1 = container.Resolve<ITask>();
task1.Complete(); // uses dev1
var task2 = container.Resolve<ITask>();
task2.Complete(); // uses dev1
container.Release(dev1); // also releases task1 and task2

var dev2 = developerFactory.CreateDeveloper("dev 2");
var task3 = container.Resolve<ITask>();
task3.Complete(); // uses dev2
// task1, task2 and task3 are always different instances

Questions:

  1. How can I configure the container to achieve this? (I intend to use version 3.0)
  2. Ideally I would not release dev1 before creating dev2 (e.g. create dev's at the same time on different threads) but I still need the container to resolve ITask's properly... Do I need to use child containers for this, or maybe the new BeginScope method in 3.0?
  3. It is also possible that a developer can handle multiple tasks at once, on different threads. So whenever I need a task I would just ask the container to resolve it and use the "contextual developer" instance... but how?

I am aware things might be easier if it were not for the circular dependency. However it seems that the model, the real one I mean, is more natural this way...

Suiden
  • 622
  • 4
  • 17
  • It might help if make things less abstract, by changing the names of `IFoo` and `IBar` to the actual names you use in the application. This way we might be able to say something useful about the actual design. – Steven Jan 22 '12 at 18:42
  • @Steven I edited the question to make things a bit less abstract. I cannot share the real model, sorry. HTH... – Suiden Jan 22 '12 at 20:13
  • Seems like `Developer` is an entity (that `IDeveloperFactory` is in fact an `IDeveloperRepository`) and it looks as if you are trying to let the container create and inject entities, which might not be that great. Take a look at this question: http://stackoverflow.com/questions/4835046/why-not-use-an-ioc-container-to-resolve-dependencies-for-entities-business-objec. – Steven Jan 22 '12 at 20:35
  • If repository is only about database, then I can assure you there is no database in this application. I was thinking of `IDeveloper` as a service which `Task` depends on. If it's a service then it should be on the container... right? – Suiden Jan 22 '12 at 21:06
  • Regarding the linked question: does this mean that whenever I use a typed factory to resolve smth which has a runtime dependency (e.g. `Developer`), I will need another factory (e.g. `ITaskFactory`) to get instances of things which depend on the previous instance? Looks like overkill for me... – Suiden Jan 22 '12 at 21:10
  • If a `Developer` has behavior, it can be considered a service and its okay to abstract it with an interface and inject it. If it only contains data, its clearly an entity and should be be created by the container. – Steven Jan 23 '12 at 08:22
  • Repositories are not only for databases. They are abstractions to load and save specific types of entities from a back-end store, such as database, file system, web service, tape drive, or what ever. – Steven Jan 23 '12 at 08:23
  • I see. I'll update `IDeveloper` to add behavior (it was omitted on purpose). Thanks for clarifying repository! – Suiden Jan 23 '12 at 08:32

1 Answers1

0

Figured it out eventually... doesn't look complicated but I'm really new to Windsor.

Container registration:

container.AddFacility<TypedFactoryFacility>();
container.Register(
    Component.For<IDeveloperFactory>().AsFactory(),
    Component.For<IDeveloper>().ImplementedBy<Developer>().LifestyleScoped(),
    Component.For<ITask>().ImplementedBy<WriteCodeTask>().LifestyleTransient(),
    Component.For<Lazy<IDeveloper>>().LifestyleTransient() // i don't use 4.0 so this is my own implementation of Lazy<> which depends on a Func<> on ctor
);

Test code:

var developerFactory = container.Resolve<IDeveloperFactory>();
using (container.BeginScope())
{
    var developer = developerFactory.Create("John"); // this creates a new Developer and caches it in the current scope
    var task1 = container.Resolve<ITask>();
    var task2 = container.Resolve<ITask>();
    Assert.Same(task1.Developer, task2.Developer);
    Assert.Equal("John", task1.Developer.Name);

    task1.Complete();
    task2.Complete();
    container.Release(task1);
    container.Release(task2);
}

This proves that both tasks could be resolved using the same contextual developer instance.

My other request was for this to work on different threads. This is ok too because if the using (container.BeginScope()) { ... } code is run on a thread then the container creates and caches a different developer instance per thread. Also I can start child threads to resolve the tasks and will still get the correct contextual developer instance.

Suiden
  • 622
  • 4
  • 17