简体   繁体   中英

How to implement a configurable singleton?

This question is related to Android but can also be asked in other situations. I need to create a library where singletons are exposed; or I want to ensure only one instance of my classes exists and can be grabbed anywhere in the code without passing references.

But thoses singletons needs some parameters. For instance in Android, a Context object is often needed. I must also precise that since I provide a library, I want things to be easy for the user and I do not have control of the Application class in Android (this class can sometimes be used to manage object instances for a whole app).

One known solution is to do the following:

static MySingleton sInstance;
MySingleton.getInstance(Context c) {
    if (sInstance == null) {
        sInstance = new MySingleton(c.getApplicationContext());
    }
    return sInstance;
}

But it is strange since the parameter of getInstance is actually only used at the first creation of the singleton.

I could provide a setter to the singleton and asks the developer to correctly set the needed parameters, but some strange situations could arise:

// Good usage
MySingleton.getInstance().setContext(context.getApplicationContext());
MySingleton.getInstance().doSomethingThatRequiresContext(); // OK

// Bad usage
MySingleton.getInstance().doSomethingThatRequiresContext(); // Error!
MySingleton.getInstance().setContext(context.getApplicationContext());

I could check at the start of each method if the singleton is correctly configured and launch some exceptions in case of a bad state, but the API is less straightforward to use:

MySingleton.getInstance().setContext(context.getApplicationContext());

try {
    MySingleton.getInstance().doSomethingThatRequiresContext();
}
catch(BadSingletonConfiguration e) { }

Even if I use runtime exceptions, it would be dangerous to use.

Unless I kindly ask the user to manually create the instance and to ensure himself only one instance will exist, I do not see a good solution.

You could have a createInstance method that takes a Context and a getInstance that will return null or throw some meaningful exception if they call getInstance before create instance. Maybe throw a RuntimeException stating that createInstance must be called first.

Also, createInstance will just return the already created instance if it has already been called. Here is a code sample of what I'm thinking:

public class MySingleton
{
    private static MySingleton INSTANCE;
    private final Context context;

    public static MySingleton createInstance(Context context)
    {
        if(INSTANCE == null)
        {
            INSTANCE = new MySingleton(context);
        }
        return INSTANCE;
    }

    public static MySingleton getInstance()
    {
        if(INSTANCE == null)
        {
            throw new RuntimeException("You must call createInstance first");
        }
        return INSTANCE;
    }

    public void doSomethingThatRequiresContext()
    {
        context.doSomething();
    }

    private MySingleton(Context context)
    {
        this.context = context;
    }
}

How about doing this instead:

  MySingleton.getInstance().doSomethingThatRequiresContext(context);

But generally it is better to use dependency injection approach instead of using manually created singletons. You may want to look at Dagger .

An alternative is to return an instance from a static init method, and have all the methods on the returned instance.

MyInstance instance = MyLibrary.init(context, and, whatever, else);
instance.doSomethingThatRequiresContext();

The order of the calls cannot be reversed now.

Then all you would potentially need to guard against is calling init twice. Which you could do either with runtime exception, or return the previous instance, personally I would Runtime if init is called twice.

I want to ensure only one instance of my classes exists and can be grabbed anywhere in the code without passing references.

Provide a getInstance that is valid only after a call to init

//MyLibrary.getInstance() would throw before init
MyInstance instance1 = MyLibrary.init(context, and, whatever, else);
MyInstance instance2 = MyLibrary.getInstance();
assertSame(instance1, instance2);

Note that although only subtly different to your original, by separating the responsibilities of allocation and singleton management in to MyLibrary , at least only the init and getInstance methods need to check that init has been called or not. None of the methods on MyInstance need to worry.

Even if I use runtime exceptions, it would be dangerous to use.

I don't think you can't solve this problem without them. It's more dangerous to not throw when something is seriously wrong, like the user has not initialized. Just add a good error message to back up your documentation.

Full listing:

public final class MyInstance {

    private final Context context;

    MyInstance(Context context, and, whatever, else) {
        //package private, can't be directly instantiated by library user
        this.context = context;
    }

    public void doSomethingThatRequiresContext() {
      //no special checks required
    }

    //add other methods, this is just a normal class
}

public static class MyLibrary {

    private static Object lockObj = new Object();

    private static MyInstance myInstance;

    public static MyInstance init(context, and, whatever, else) { 
        synchronized(lockObj) {
            if (myInstance != null)
                throw new RuntimeException("Please do not call init more than once");
            myInstance = new MyInstance(context, and, whatever, else);
            return myInstance;
        }
    }

    public static MyInstance getInstance() {
        synchronized(lockObj) {
            if (myInstance == null)
                throw new RuntimeException("Please call init before getInstance");
            return myInstance;
        }
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM