getInstance()是否表示单例模式中的单例?

11

之前我曾经使用过C#,我考虑过

Calendar cal = Calendar.getInstance();

根据GoF 单例模式(维基百科)的定义,要创建两个日历,因为Date有点过时,需要成为单例方法。
从文档中可以得到:

使用默认时区和语言环境获取日历。

还有其他重载方法。
getInstance(TimeZone zone)
getInstance(Locale aLocale)

这似乎是将单例模式推广到每个时区和语言环境来创建单例的一般化。但我想在同一个时区中拥有两个日历。
然而,当我进行测试时。
@Test
public void test()
{
    Calendar cal = Calendar.getInstance();
    Calendar cal2 = Calendar.getInstance();
    assertTrue(cal == cal2); 
}

它失败了,告诉我这个 getInstance() 方法实际上不是单例模式的 getInstance() 方法,而是其他东西。那么,一般情况下,getInstance() 是否符合 Java 中的单例模式?如果是,Java 文档中有哪些关键词可以找到它是否是单例模式?如果不是,我该如何在 Java 中识别单例模式?或者只有 Calendar 是例外吗?我不想每次遇到 getInstance() 调用时都要实现一个单元测试。我已经阅读了 this answer to "What is an efficient way to implement a singleton pattern in Java?",它将像 C# 一样工作,这与 Calendar 的实现相矛盾。

4
这只是一个方法命名不当。它总是创建一个新实例,名称中或至少在API文档中应该有一些表明这一事实的指示。 - biziclop
1
通常情况下,可以说getInstance是一个工厂方法,它可能返回一个单例。没有关键字可以告诉我们返回的对象是否将成为单例。 - Shailesh Aswal
非单例指当前时间。 - Prashant
1
https://dev59.com/vXI-5IYBdhLWcg3wy7sd - user5011070
这只是一个巧合。getInstance() 可以表示获取一个实例(工厂模式)或获取唯一实例(单例模式)。其他模式中也有类似 visit()create()notify() 等方法,但不能假设使用这些方法就意味着应用了访问者模式、工厂方法或观察者模式。设计模式不仅仅是一个方法名。 - Fuhrmanator
5个回答

11

Calendar不是单例模式,每次调用Calendar.getInstance(...)都会返回一个不同的实例。Javadoc没有说每次调用都会返回相同的实例,因此你不能假设它会这样做。

Calendar.getInstance(...)更适合工厂设计模式。

查看其他getInstance的示例,如Locale.getInstance(),您会发现Javadoc告诉您连续调用是否可能返回相同的实例:

/**
 * Returns a <code>Locale</code> constructed from the given
 * <code>language</code>, <code>country</code> and
 * <code>variant</code>. If the same <code>Locale</code> instance
 * is available in the cache, then that instance is
 * returned. Otherwise, a new <code>Locale</code> instance is
 * created and cached.
 *
 * @param language lowercase two-letter ISO-639 code.
 * @param country uppercase two-letter ISO-3166 code.
 * @param variant vendor and browser specific code. See class description.
 * @return the <code>Locale</code> instance requested
 * @exception NullPointerException if any argument is null.
 */
static Locale getInstance(String language, String country, String variant)

再次强调,这不是单例模式,但是实例会被缓存,正如Javadoc中所说的。除非Javadoc明确说明,否则每次调用getInstance都会返回一个不同的实例。


其他依赖于区域设置的类与日历之间的区别在于,日历是可变的,因此绝对不能被缓存。使用相同的方法名称来表示两种非常不同的行为是一个糟糕的决定。 - biziclop
1
@biziclop 这是一个很好的观点。由于Calendar的Javadoc没有提到getInstance返回缓存实例或单例实例,我们不应该假设任何一种情况。我认为我们不必查看实现。如果我们这样做,这意味着Javadoc存在缺陷。 - Eran
1
准确来说,Calendar.getInstance() 是一个静态工厂方法(由Josh Bloch提出),它与GoF工厂方法模式完全不同。 - jaco0646

2

getInstance()方法通常表示Java中的单例模式,但这并不是铁板钉钉的规则。要确定它是否是单例模式,您必须查看文档(如果有)或更好的方法是查看实现代码。


1
其中最难解释的事情之一是模式和示例之间的关系。大多数模式描述都包括示例代码,但重要的是要意识到代码示例并不是模式本身。 -- Martin Fowler - Fuhrmanator

1

不是的,如果你查看源代码,你会发现总是得到一个new Calendar,而getInstace()方法只是确定你将得到哪种类型的Calendar

public static Calendar More ...getInstance(TimeZone zone, Locale aLocale)
{
    return createCalendar(zone, aLocale);
}

并且 createCalendar

private static Calendar More ...createCalendar(TimeZone zone, Locale aLocale)
{
    // If the specified locale is a Thai locale, returns a BuddhistCalendar instance.
    if ("th".equals(aLocale.getLanguage())
        && ("TH".equals(aLocale.getCountry()))) {
        return new sun.util.BuddhistCalendar(zone, aLocale);  // new Budhist
    } else if ("JP".equals(aLocale.getVariant())
               && "JP".equals(aLocale.getCountry())
               && "ja".equals(aLocale.getLanguage())) {
        return new JapaneseImperialCalendar(zone, aLocale); // new Japanese
    }

    // else create the default calendar
    return new GregorianCalendar(zone, aLocale);   // new Gregorian
}

如何在Java中确定单例模式?

单例模式总是返回相同的对象实例,有不同的方法,但通常你会有:

  • 类属性(获取实例的对象)
  • 私有构造函数以避免新建实例
  • 公共getInstance方法,检查属性是否为空来创建它,并且始终返回相同的对象实例...

类似于:

public class Singleton {
    // instance attribute
    private static Singleton INSTANCE = null;

    // private constructor
    private Singleton() {}

    // public get that checks instance and return it
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

如果我必须查看每个getInstance()的源代码,那么这甚至比编写单元测试还要糟糕... - Thomas Weller
1
阅读“API”或查看源代码是确保的唯一方法... getInstance是一个名称,而不是Java约定... - Jordi Castilla

0

由于Calendar.getInstance()返回的对象指向某个时间(更具体地说,是创建该对象的日期和时间),因此它不应该是单例的。如果是这种情况,所有对象都需要在执行操作时更新其基础日期/时间。

然而,根据我的理解,getInstance()之所以被命名为这样是因为它是一个工厂方法。您无需直接创建实例,但可以使用某些方法来指定任何日期/时间。


0

不是的。

getInstance() 在 Java 中应该只表示一件事情,那就是你将获得给定类或其子类的一个实例。它是否适合单例模式或工厂模式在不同的类和库之间有很大的差异。

根据我的经验,在标准库代码 java.* 中,getInstance() 通常表示工厂模式,可以使用区域设置默认值(例如 CalendarNumberFormatDateFormat)来配置/子类化新实例,或者查找给定某些参数的预先存在的实例(例如 LocaleCurrency)。 在第三方库代码中,可能会倾向于使用单例模式。

至于 Calendar.getInstance() 的具体细节,非参数变量将为您提供 Calendar 的子类实例(可能是 GregorianCalendarBuddhistCalendarJapaneseCalendar),具体取决于默认区域设置的时区。带有参数的变量将允许您指定区域设置和/或时区,而不是从默认值中获取。

返回的实例是否确实是单例,是否被缓存或其他实现特定。文档通常会指示返回的实例是否可变,如果是,则可以安全地在线程之间共享。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接