简体   繁体   English

如何实现可配置的单例?

[英]How to implement a configurable singleton?

This question is related to Android but can also be asked in other situations. 此问题与Android有关,但在其他情况下也可以询问。 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. 例如,在Android中,经常需要Context对象。 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). 我还必须确切地说,因为我提供了一个库,所以我希望用户能够轻松操作并且我无法控制 Android 中的Application (此类有时可用于管理整个应用程序的对象实例)。

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. 但这很奇怪,因为getInstance的参数实际上只在第一次创建单例时使用。

I could provide a setter to the singleton and asks the developer to correctly set the needed parameters, but some strange situations could arise: 我可以为单例提供一个setter并要求开发人员正确设置所需的参数,但可能会出现一些奇怪的情况:

// 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: 我可以检查每个方法的开头是否正确配置了单例,并在状态错误的情况下启动了一些异常,但API的使用不太简单:

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. 你可以有一个createInstance方法,它接受一个Context和一个getInstance,如果它们在create instance之前调用getInstance,它将返回null或抛出一些有意义的异常。 Maybe throw a RuntimeException stating that createInstance must be called first. 也许抛出一个RuntimeException,声明必须先调用createInstance。

Also, createInstance will just return the already created instance if it has already been called. 此外,如果已经调用了createInstance,它将只返回已创建的实例。 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 . 你可能想看看Dagger

An alternative is to return an instance from a static init method, and have all the methods on the returned instance. 另一种方法是从静态init方法返回一个实例,并在返回的实例上包含所有方法。

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. 然后你可能需要防范的是调用init两次。 Which you could do either with runtime exception, or return the previous instance, personally I would Runtime if init is called twice. 您可以使用运行时异常执行哪个操作,或者返回上一个实例,如果init被调用两次,我个人会运行Runtime。

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 提供仅在调用init后才有效的getInstance

//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. 请注意,虽然只是略微不同于您的原始,但通过将分配和单例管理的职责分离到MyLibrary ,至少只有 initgetInstance方法需要检查是否已调用init。 None of the methods on MyInstance need to worry. MyInstance上没有任何方法需要担心。

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;
        }
    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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