如何实现可配置的单例?

时间:2016-01-06 20:29:24

标签: java android oop design-patterns

此问题与Android有关,但在其他情况下也可以询问。我需要创建一个暴露单身人士的图书馆;或者我想确保只存在我的类的一个实例,并且可以在代码中的任何位置抓取而不传递引用。

但是单身人士需要一些参数。例如,在Android中,经常需要Context个对象。我还必须准确一点,因为我提供了一个库,我希望用户能够轻松完成任务并且我无法控制Android中的Application(有时可以使用此类管理整个应用程序的对象实例。

一种已知的解决方案是执行以下操作:

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

但很奇怪,因为getInstance的参数实际上只在第一次创建单身时使用。

我可以为单例提供一个setter并要求开发人员正确设置所需的参数,但可能会出现一些奇怪的情况:

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

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

我可以检查每个方法的开头是否正确配置了单例,并在状态不好的情况下启动了一些异常,但API使用起来不太直接:

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

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

即使我使用运行时异常,使用它也会很危险。

除非我恳请用户手动创建实例并确保自己只存在一个实例,否则我看不到一个好的解决方案。

3 个答案:

答案 0 :(得分:2)

你可以有一个createInstance方法,它接受一个Context和一个getInstance,如果它们在create instance之前调用getInstance,它将返回null或抛出一些有意义的异常。也许抛出一个RuntimeException,声明必须先调用createInstance。

此外,如果已经调用了createInstance,它将只返回已创建的实例。这是我正在思考的代码示例:

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

答案 1 :(得分:1)

如何做到这一点:

  MySingleton.getInstance().doSomethingThatRequiresContext(context);

但通常最好使用依赖注入方法而不是使用手动创建的单例。您可能需要查看Dagger

答案 2 :(得分:1)

另一种方法是从静态init方法返回一个实例,并在返回的实例上包含所有方法。

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

现在无法撤消的调用顺序

然后你可能需要防范的是两次调用init。您可以使用运行时异常执行哪个操作,或者返回上一个实例,如果init被调用两次,我会个人运行。

  

我想确保只存在一个类的实例,并且可以在代码中的任何位置抓取而不传递引用。

提供仅在致电getInstance

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

请注意,虽然只是略微不同于原作,但将分配和单身人士管理的职责分为MyLibrary,至少 init和{{1方法需要检查是否已调用init。 getInstance上的所有方法都不用担心。

  

即使我使用运行时异常,使用它也会很危险。

如果没有它们,我认为你无法解决这个问题。如果用户没有初始化,那么在出现严重错误时不抛出会更危险。只需添加一条好的错误消息即可备份文档。

完整列表:

MyInstance