How to implement custom singleton?

This question is related to Android, but it can also be asked in other situations. I need to create a library where singletones will be shown; or I want to provide only one instance of my classes and can be captured anywhere in the code without passing links.

But thoses singles need some parameters. For example, Android often requires a Context object. I also need to clarify that since I provide the library, I want everything to be easy for the user, and I have no control over the Application class in Android (this class can sometimes be used to control object instances for the whole application).

One well-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 this is strange, since the getInstance parameter is actually used only during the first creation of singleton code.

I could provide a setter to a single element and asked the developer to set the necessary parameters correctly, but some strange situations may 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 beginning of each method if the singleton is configured correctly and throw some exceptions in case of a bad state, but the API is less easy to use:

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

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

If I ask the user to manually create an instance and make sure that only one instance exists, I do not see a good solution.

+5
source share
3 answers

You can have a createInstance method that takes a context and getInstance that return null or throw any meaningful exception if they call getInstance before creating the instance. Maybe throw a RuntimeException, indicating that you need to call createInstance first.

In addition, createInstance will simply return an already created instance if it has already been called. Here is an example of the code I'm thinking of:

 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; } } 
+2
source

How to do it:

  MySingleton.getInstance().doSomethingThatRequiresContext(context); 

But, as a rule, it is better to use the injection injection approach instead of using manually created singletons. You can watch Dagger .

+1
source

An alternative is to return the instance from the static init method and use all methods of the returned instance.

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

Now the call order cannot be canceled.

Then, all you need to protect is calling init twice. What you could do with either an exception at runtime or return the previous instance, I personally would run Runtime if init is called twice.

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

Specify getInstance , which is valid only after calling init

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

Please note that although it differs only in the subtlety of your original, separating the responsibilities of distributing and managing singleton to MyLibrary , at least only the init and getInstance should verify that init was called or not, None of the MyInstance methods should bother.

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

I do not think that you cannot solve this problem without them. It is more dangerous to quit when something is seriously wrong, as the user did not initialize. Just add a good error message to back up the documentation.

Full list:

 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; } } } 
+1
source

Source: https://habr.com/ru/post/1240028/


All Articles