SimpleServiceLocator: Why is automatic constructor

2019-08-01 08:30发布

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)?

2条回答
可以哭但决不认输i
2楼-- · 2019-08-01 08:56

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.

Update:

Since release 0.14 we can do the following:

container.RegisterSingle<IWeapon, Katana>();

It won't get any easier than this.

Cheers

查看更多
欢心
3楼-- · 2019-08-01 08:56

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.

查看更多
登录 后发表回答