views:

56

answers:

2

Consider the following example:

public interface ITask
{
    void Execute();
}

public class LoggingTaskRunner : ITask
{
    private readonly ITask _taskToDecorate;
    private readonly MessageBuffer _messageBuffer;

    public LoggingTaskRunner(ITask taskToDecorate, MessageBuffer messageBuffer)
    {
        _taskToDecorate = taskToDecorate;
        _messageBuffer = messageBuffer;
    }

    public void Execute()
    {
        _taskToDecorate.Execute();
        Log(_messageBuffer);
    }

    private void Log(MessageBuffer messageBuffer)
    {}
}

public class TaskRunner : ITask
{
    public TaskRunner(MessageBuffer messageBuffer)
    {

    }

    public void Execute()
    {
    }
}

public class MessageBuffer
{

}


public class Configuration
{
    public void Configure()
    {
        IWindsorContainer container = null;

        container.Register(
            Component.For<MessageBuffer>()
                .LifeStyle.Transient);

        container.Register(
            Component.For<ITask>()
                .ImplementedBy<LoggingTaskRunner>()
                .ServiceOverrides(ServiceOverride.ForKey("taskToDecorate").Eq("task.to.decorate")));

        container.Register(
            Component.For<ITask>()
            .ImplementedBy<TaskRunner>()
            .Named("task.to.decorate"));

    }

}

How can I make Windsor instantiate the "shared" transient component so that both "Decorator" and "Decorated" gets the same instance?

Edit: since the design is being critiqued I am posting something closer to what is being done in the app. Maybe someone can suggest a better solution (if sharing the transient resource between a logger and the true task is considered a bad design)

+1  A: 

'Transient' explicitly means 'non-shared', so what you are asking is conceptually the wrong thing to do. The correct solution is to register Shared as a Singleton instead of Transient:

container.Register(Component.For<Shared>());

(Singleton is the default lifetime in Windsor.)

However, I suspect that behind the stated question lies a much more complex problem. I'm guessing that you need Shared to be Transient because you need it with this lifestyle for a lot of other cases, but exactly when it comes to the relationship between Decorator and Decorated you need to share them.

I still think this sounds like a Design Smell, but there are at least two ways you can achieve this result.

The first option involves prematurely resolving Shared and explicitly supply the resolved instance to the configuration of the two IFoo registrations:

container.Register(Component.For<Shared>().LifeStyle.Transient);

var r = container.Resolve<Shared>();

container.Register(Component
    .For<IFoo>()
    .ImplementedBy<Decorator>()
    .DependsOn(new { resource = r }));
container.Register(Component
    .For<IFoo>()
    .ImplementedBy<Decorated>()
    .DependsOn(new { resource = r }));

The second option is to make a specialized, named registration for Shared that is used only by the IFoo registrations:

container.Register(Component.For<Shared>().LifeStyle.Transient);
container.Register(Component.For<Shared>().Named("shared"));
container.Register(Component
    .For<IFoo>()
    .ImplementedBy<Decorator>()
    .ServiceOverrides(new { resource = "shared" }));
container.Register(Component
    .For<IFoo>()
    .ImplementedBy<Decorated>()
    .ServiceOverrides(new { resource = "shared" }));
Mark Seemann
I don't want to make "Shared" non-transient because it has state. I also don't want to remove it as a constructor dependency because its an invariant which has to be satisfied, making it into a property instead of a ctor dependency would be retrofitting the class design to fit the workings of an IoC container.
Marius
Concern about state is a very valid reason why you would not want to share instances, but `Shared` *is* shared between Decorator and Decorated, so you already violate that principle. Your current implementation may behave 'nicely' and not corrupt state, but other collaborators might not be so nice. In other words, implementations may break if injected with different dependencies. That indicates a violation of the Liskov Substitution Principle.
Mark Seemann
You are misenterpreting the usage. It is valid for any class which has a reference to a messagebuffer to utilize its contract and by that I mean adding messages. I'm am not assuming anything about the usage of the messagebuffer, thus I am not breaking Liskov.
Marius
A: 

I found a third solution to this:

        container.Register(
            Component.For<MessageBuffer>()
                .LifeStyle.Transient);

        container.Register(
            Component.For<ITask>()
                .ImplementedBy<LoggingTaskRunner>()
                .DynamicParameters((k, d) =>
                                       {
                                           MessageBuffer buffer = container.Resolve<MessageBuffer>();

                                           Dictionary<string, object> overridenComponents = new Dictionary<string, object>();
                                           overridenComponents.Add("messageBuffer", buffer);

                                           d["messageBuffer"] = buffer;
                                           d["taskToDecorate"] = container.Resolve<ITask>("task.to.decorate", overridenComponents);
                                       }));

        container.Register(
            Component.For<ITask>()
            .ImplementedBy<TaskRunner>()
            .Named("task.to.decorate"));

I'm not sure which solution i like the best. Marks option does not contain any string-literals (++), but will not work nicely if you change the ITasks to be transient (all instances will share the same message buffer instance). My solution is more robust against changes to Task lifestyles and doesn't resolve any types before requested, but contain those horrific string-literals..

Marius

related questions