views:

103

answers:

2

I've been experimenting with the SimpleServiceLocator, and I like it quite a bit, but there's one thing that I'm really frustrated by--you can't use automatic constructor injection for singletons. To make matters worse, you can't even use automatic constructor injection for its dependencies. You have to create the singleton object, all it's dependencies, all its dependencies dependencies, etc. manually.

Why is SimpleServiceLocator designed this way?

Aren't singletons supposed to be just like regular instances except that, upon the first request for an instance, that instance is stored and reused instead of a new one being created each time? Why does SimpleServiceLocator require an instance to be provided during the registration process rather than just allow the instance to be created and stored on first request?

I get that the point of SimpleServiceLocator is to not have a lot of bells and whistles and be really easy for beginners to use, but it seems like it's just designed incorrectly, and that the method for registering a singleton should be identical to the method for registering a regular instance except that the method name should be RegisterSingle<T>() instead of Register<T>(). Is there a reason for the more complicated (and seemingly less convenient) design I'm just not getting?

Meanwhile, is there another (preferably free) IOC container I can use that let's me register objects in code similarly to the SimpleServiceLocator but does allow automatic contructor injection for singletons (or at least allows automatic constructor injection for the dependencies of the singleton)?

A: 

A typical singleton implementation has a private constructor, so the container cannot "see" it, call it, or detect dependencies.

Perhaps you are referring to the lifetime management features of some IoC containers, where you can configure the container to always return the same single instance of a class.

This is not what singleton means. Although the container returns the same instance, nothing prevents you from instantiating an instance in code using new.

A singleton, on the other hand, can only ever be instantiated once from any source (once per thread in some implementations). It does not expose a public constructor, rather a static method such as:

public class MySingleton
{
    // note: not a thread-safe implementation
    static MySingleton instance;
    static DependencyThing thing;

    private MySingleton(DependencyThing thing)
    {
        MySingleton.thing = thing;
    }

    public static MySingleton GetMySingleton(DependencyThing thing)
    {
        if(instance == null) instance = new MySingleton(thing);
        return instance;
    }
}

As you can see, you can't call new MySingleton() from outside the class itself. To "instantiate" the a MySingleton, you have to call MySingleton.GetMySingleton(thing). This call returns the sole instance or creates and then returns it.

SimpleServiceLocator has no way of knowing how to create this object, or from where to detect its dependencies.

This ability could be added if the API exposed something like

public void Register<T>(Expression<Func<T>> staticFactoryMethod)…

…in which case you could call Register(() => MySingleton.GetMySingleton());, but this would only work without parameters. There would have to be more overloads:

public void Register<T, TParam1>(Expression<Func<TParam1, T>> staticFactoryMethod)…
public void Register<T, TParam1, TParam2>(Expression<Func<TParam1, TParam2, T>> staticFactoryMethod)…

…so that the container would know what dependencies to instantiate and pass to the specified factory method.

All that said, it doesn't really make sense to have dependency injection with a singleton. Each subsequent call to GetMySingleton would have to ignore the arguments or alter the state of the singleton, which is almost certainly a very bad idea.

Jay
Thanks, Jay +1. *Perhaps you are referring to the lifetime management features of some IoC containers, where you can configure the container to always return the same single instance of a class.* Yes, that's exactly the feature I need. The classes I'm instantiating are not technically singletons (there is no *guarantee* they can only be instantiated once), it's just that in some cases, I need certain classes to share the same instance of an object or it's inefficient to instantiate an object more than once.
DanM
That said, it still doesn't make sense to me that I can't manually create my singletons but use automatic constructor injection for the dependencies. Any explanation for that one?
DanM
@DanM In any IoC container I've used (several, but by no means all), registering single-instance lifetime always requires passing in that instance. Say the subject type takes 2 constructor parameters, `ThingA` and `ThingB`. If you have registered `ThingA` and `ThingB`, you can call `container.RegisterSingle(new MySingleton(container.getInstance<ThingA>(), container.getInstance<ThingB>())`
Jay
That's exactly what I tried to do, but I kept getting the error: "The SimpleServiceLocator can't be changed after the first call to GetInstance and GetAllInstances." So, I think I'm going to need to upgrade to a slightly more sophisticated IOC container.
DanM
No, you don't have to upgrade. Please look at my answer (comming shortly).
Steven
@Jay: Dan is talking about the 'singleton lifestyle'. This is a common concept for IoC containers which means that the container will always return the same instance. Besides talking about a single instance, this has nothing to do with the singleton design pattern.
Steven
@Steven Out of curiosity, what is the rationale for locking down the container after a call to get an instance?
Jay
@Jay: By locking down the container, I force users to have the complete container configured before its first use. IMO this is a good model, because it makes the application easier to understand. Locking down also has the advantage of being able to synchronize read and write access to the container, which always has impact on performance.
Steven
+2  A: 

Hi Dan,

The RegisterSingle<T> method is just a fancy helper method just to make life easier. What you can do with RegisterSingle<T> can also be done with the Register<T> method. The web site gives examples of this. You can register a single instance using the Register<T> method as follows (it uses a closure):

var weapon = new Katana();
container.Register<IWeapon>(() => weapon);

When you look at the lifestyle management examples on the web site, you can see the following example for creating a thread static instance:

[ThreadStatic]
private static IWeapon weapon;

container.Register<IWeapon>(
    () => return weapon ?? (weapon = new Katana()));

I think this is the power of simplify, because there is almost nothing you can't do with this pattern. What you are trying to achieve is a bit harder, I must admit this, but nothing really advanced IMO. Here is the code you need to solve your problem:

private static IWeapon weapon;

container.Register<IWeapon>(
    () => weapon ?? (weapon = container.GetInstance<Katana>()));

The trick is here to store the instance in a static variable (just as with the thread static), but now you should not create the instance yourself by newing it up, but you delegate the creation to the Simple Service Locator. This works, because –as you know- the SimpleServiceLocator will do automatic constructor injection when a concrete type is requested.

I must admit that it is a shame that we need to do this trickery. It would be nice if the library could actually do this for us. For instance, I can imagine a RegisterSingle<T> overload being added that allows us to do the following:

container.RegisterSingle<IWeapon>(
    () => container.GetInstance<Katana>());

Please let me know what you think of such an overload. I'm always interested in feedback to make the library better. This would certainly be a nice feature for the next release.

Cheers

ps. I understand you use Stackoverflow for this question, but at least send me a private message through Codeplex or post a link to this question at the Simple Service Locator discussion forum. That way I can respond sooner, because now I accidentally came across this question.

Steven
+1. Thanks for this answer, Steven. I will definitely give that a try. The overload sounds like a great idea. I actually posted the same question to Codeplex as I did here, but then when I got Jay's answer, I realized I wasn't technically working with singletons, so I decided to delete it. I can put put it back if you like, though.
DanM
@Dan: I've added the `RegisterSingle<T>(Func<T>)` overload and published Simple Service Locator v0.8 that contains this overload. I hope this helps.
Steven
This, is great Steven, thanks. Can't wait to try it out. Hopefully in the next day or two.
DanM
Steve, just tried out the new overload, and it works like a charm. I was able to simplify my code significantly, and the "locking out" problem I was having is now a non-issue. Thanks for your help and for adding this new feature so quickly!
DanM
Steven, I did just notice one little oddity. If you do something like this: `container.Register<Katana>(() => container.GetInstance<Katana>())`, you get a `StackOverflowException`. This of course make sense (the method for creating A is to create A --> infinite recursion), but it makes it impossible to do automatic constructor injection with the new `RegisterSingle()` overload. One solution for this would be to write some code in the locator that checks if `Katana` has already been created, then use the `Katana` in memory instead of calling the delegate again. What do you think?
DanM
Dan, the solution is much much simpler: Do NOT register concrete type `Katana`. The `StackOverflow` is interesting, I didn't think of this. I will try to see if I can make an error check for this. Of course, as you said, it makes sense that the exception is thrown. The reason that you don't have to register the type is because the concrete type can be created because of the constructor injection. It would make no sense registering the type that the container can figure out itself. Still the SO is interesting.
Steven
@Steven, I just realized I made a mistake in my comment. The statement that's causing the problem is `container.RegisterSingle<Katana>(() => container.GetInstance<Katana>())` (note the `Single`). So, in other words, I'm not registering the type to say *how* it should be created but to say that only *one* should be created. Sorry for the confusion. (P.S. Please use @Dan or @DanM to make sure I'm notified of your comment.)
DanM
@DanM: You are really taking the Simple Service Locator to its limits :-). There is currently absolutely no way to do this, expect of course doing this: `container.RegisterSingle<Katana>(() => new Katana(container.GetInstance<IWeapon>()))`, but this is something you probably don't want. Expect a solution to this problem soon ;-). I keep you posted.
Steven
@DanM: Please go to http://simpleservicelocator.codeplex.com/releases/ to fetch the latest release. v0.9 now contains a `RegisterSingle<Samurai>()` overload that does exactly as you wish.
Steven
Hi Steven, Just tried it, and it works great. Thanks again! And what a nice, clean way to implement it. Even though you've added a couple overloads, I think you've actually made the product simpler and easier to use because the interface now makes it obvious how to create single instances using automatic constructor injection.
DanM
@DanM: Thanks for the compliment. And thanks for helping me making SSL better. If there's anything else, please let me know.
Steven
@Steven, Looks like SSL could use one more overload: `RegisterSingleByKey<T>(string key, Func<T> singleInstanceCreator)`. What do you think?
DanM
Yes, I know. I was hoping you didn't need it :-). I will try to add such a feature soon. In the mean time, this example will help you out: http://simpleservicelocator.codeplex.com/wikipage?title=RegisteringSingletonsByKey.
Steven
@Steven, I only just started needing it today :) Thanks for the example.
DanM
@DanM: I just released v0.10. This release contains the missing `RegisterSingleByKey<T>(string, Func<T>)` overload. I added two other overloads: `RegisterSingleByKey<T>(Func<string, T>)` and `RegisterByKey<T>(string, Func<string>)`. I had to do change some significant internal changes to get this all running. I hope you like it. Have fun with it ;-) http://simpleservicelocator.codeplex.com/releases/view/52884
Steven
@Steven, Sounds great, thanks! Will take a look soon.
DanM
@Steven, just plugged the new version in. Works great. Thanks again!
DanM
@DanM: I like your opinion on a new overload of `Register` now taking an `Action<T>`: http://simpleservicelocator.codeplex.com/workitem/14312. What do you think of the name? Is it expressive enough? You can comment at codeplex if you like. Thanks
Steven
@Steven, I added a comment to your item. Also created a new item for an idea I had today about "cascading keys".
DanM