28

Here is my sample abstract singleton class:

public abstract class A {
    protected static A instance;
    public static A getInstance() {
        return instance;
    }
    //...rest of my abstract methods...
}

And here is the concrete implementation:

public class B extends A {
    private B() { }
    static {
        instance = new B();
    }
    //...implementations of my abstract methods...
}

Unfortunately I can't get the static code in class B to execute, so the instance variable never gets set. I have tried this:

Class c = B.class;
A.getInstance() - returns null;

and this

ClassLoader.getSystemClassLoader().loadClass("B");
A.getInstance() - return null;

Running both these in the eclipse debugger the static code never gets executed. The only way I could find to get the static code executed is to change the accessibility on B's constructor to public, and to call it.

I'm using sun-java6-jre on Ubuntu 32bit to run these tests.

Simon
  • 563
  • 1
  • 6
  • 11

6 Answers6

22

Abstract Singleton? Doesn't sound viable to me. The Singleton pattern requires a private constructor and this already makes subclassing impossible. You'll need to rethink your design. The Abstract Factory pattern may be more suitable for the particular purpose.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • The purpose of the private constructor in the Singleton is to prevent anyone else instantiating it. I have achieved this here - you can't instantiate an abstract class, and the subclass has a private constructor. – Simon Mar 17 '10 at 00:08
  • It doesn't force the subclass to be abstract-only and have a private constructor only. – BalusC Mar 17 '10 at 00:13
  • Singleton doesn't _require_ a private constructor (see my answer for a solution to this question with a public constructor). – ryvantage Sep 13 '15 at 18:26
  • 7
    Sorry, but your answer is useless, ryvantage. The private constructor is a key requirement of the singleton pattern. If you don't have one, then you actually don't have a singleton. Food for read: http://butunclebob.com/ArticleS.UncleBob.SingletonVsJustCreateOne – BalusC Sep 13 '15 at 19:29
  • 1
    [It is a common misconception that the Singleton design pattern prohibits inheritance.](https://stackoverflow.com/a/60496700/1371329) – jaco0646 Mar 04 '20 at 15:39
8

You are trying to get an abstract class play two very different roles:

  • the abstract factory role for a (singleton) service that can have multiple substitutable implementations,
  • the service interface role,

and on top of that you also want the service to be singleton and enforce 'singletoness' on the entire family of classes, for some reason you aren't considering caching the service instances.

Somebody (I would) will say it smells very bad, for multiple reasons it violates separation of concerns, singletons make unit testing impossible", etc.

Somebody else will say it's ok-ish, it doesn't need a lot of different infrastructure and has kind of fluent-ish interface that you see in some very common third party (legacy) Java API.

The bad part is demanding the children to select what implementation should the parent factory method return. That responsibility should be pushed up and centralised into the abstract superclass. Otherwise you are mixing together patterns that are used in very different contexts, Abstract Factory (parent decide what family of classes clients are going to get) and Factory Method (children factories select what the clients will get).

Factory Method is also not practically possible because you can't override static methods, nor constructors.

There are some (ugly) ways to achieve your objective though:

public abstract class A{
    public static A getInstance(...){
      if (...)
         return B.getInstance();
      return C.getInstance();
    }

    public abstract void doSomething();

    public abstract void doSomethingElse();

}

public class B extends A{
    private static B instance=new B();

    private B(){
    }

    public static B getInstance(){
        return instance;
    }

    public void doSomething(){
        ...
    }
    ...
}

//do similarly for class C

The parent could also use reflection, cache instances, etc.

A more test and extension friendly solution is simply standard separation of concerns. The children aren't going to be singleton anymore per se, but you package them into some internal package that you will document as "private" and a public abstract parent in an external package will handle caching or pooling of children instances, enforcing whatever instantiation policy is required on these classes.

4

A.getInstance() will never call a derived instance since it's statically bound.

I would separate the creation of the object from the actual object itself and create an appropriate factory returning a particular class type. It's not clear how you'd parameterise that, given your example code - is it parameterised via some argument, or is the class selection static ?

You may want to rethink the singleton, btw. It's a common antipattern and makes testing (in particular) a pain, since classes under test will provide their own instance of that class as a singleton. You can't provide a dummy implementation nor (easily) create a new instance for each test.

Brian Agnew
  • 268,207
  • 37
  • 334
  • 440
  • A.getInstance() does not need to call a derived instance, since it returns the instance variable. The derived instance (class B) sets the instance variable. Except that the static code block does not get run. If I can't fix this then a Factory pattern may be my only option. – Simon Mar 17 '10 at 00:16
3

Singletons are kind of yucky. Abstract insists on inheritance which you more often than not want to avoid if possible. Overall I'd rethink if what you are trying to do is the simplest possible way, and if so, then be sure to use a factory and not a singleton (singletons are notoriously hard to substitute in unit tests whereas factories can be told to substitute test instances easily).

Once you start looking into implementing it as a factory the abstract thing will sort itself out (either it will clearly be necessary or it may factor out easily in place of an interface).

Community
  • 1
  • 1
Bill K
  • 62,186
  • 18
  • 105
  • 157
  • 1
    Singletons have their place but there are usually a better design pattern for whatever you are doing. A log is probably the best example that I can think of for a singleton - this way you don't have to pass a variable and can access it from anywhere. – Natalie Adams Apr 17 '13 at 19:30
  • A log can be done much better with Injection. A singleton log cannot easily tell you what class the line came from whereas Log l=new Log(this.getClass()) or some varient thereof can. With injection it's just @inject log or something similar. A singleton is also harder to test with, even for logging. Again a singleton is just about the worst choice (but admittedly usable) – Bill K Apr 17 '13 at 21:42
3

In addition to problems others have pointed out, having the instance field in A means that you can only have one singleton in the entire VM. If you also have:

public class C extends A {
    private C() { }
    static {
        instance = new C();
    }
    //...implementations of my abstract methods...
}

... then whichever of B or C gets loaded last will win, and the other's singleton instance will be lost.

This is just a bad way to do things.

Matt McHenry
  • 20,009
  • 8
  • 65
  • 64
  • Thanks - and the others who said the same thing. This highlights that my real problem is how I select which one of several implementations of my interface I should expose. Factory or Facade look like good ways to go. – Simon Mar 17 '10 at 00:35
1

I found a better way to use Singleton in abstract class, which use a static Map to maintain the instance of subclass.

public abstract class AbstractSingleton {

    private static Map<String, AbstractSingleton> registryMap = new HashMap<String, AbstractSingleton>();

    AbstractSingleton() throws SingletonException {
        String clazzName = this.getClass().getName();
        if (registryMap.containsKey(clazzName)) {
            throw new SingletonException("Cannot construct instance for class " + clazzName + ", since an instance already exists!");
        } else {
            synchronized (registryMap) {
                if (registryMap.containsKey(clazzName)) {
                    throw new SingletonException("Cannot construct instance for class " + clazzName + ", since an instance already exists!");
                } else {
                    registryMap.put(clazzName, this);
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    public static <T extends AbstractSingleton> T getInstance(final Class<T> clazz) throws InstantiationException, IllegalAccessException {
        String clazzName = clazz.getName();
        if (!registryMap.containsKey(clazzName)) {
            synchronized (registryMap) {
                if (!registryMap.containsKey(clazzName)) {
                    T instance = clazz.newInstance();
                    return instance;
                }
            }
        }
        return (T) registryMap.get(clazzName);
    }

    public static AbstractSingleton getInstance(final String clazzName)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        if (!registryMap.containsKey(clazzName)) {
            Class<? extends AbstractSingleton> clazz = Class.forName(clazzName).asSubclass(AbstractSingleton.class);
            synchronized (registryMap) {
                if (!registryMap.containsKey(clazzName)) {
                    AbstractSingleton instance = clazz.newInstance();
                    return instance;
                }
            }
        }
        return registryMap.get(clazzName);
    }

    @SuppressWarnings("unchecked")
    public static <T extends AbstractSingleton> T getInstance(final Class<T> clazz, Class<?>[] parameterTypes, Object[] initargs)
            throws SecurityException, NoSuchMethodException, IllegalArgumentException,
            InvocationTargetException, InstantiationException, IllegalAccessException {
        String clazzName = clazz.getName();
        if (!registryMap.containsKey(clazzName)) {
            synchronized (registryMap) {
                if (!registryMap.containsKey(clazzName)) {
                    Constructor<T> constructor = clazz.getConstructor(parameterTypes);
                    T instance = constructor.newInstance(initargs);
                    return instance;
                }
            }
        }
        return (T) registryMap.get(clazzName);
    }

    static class SingletonException extends Exception {
        private static final long serialVersionUID = -8633183690442262445L;

        private SingletonException(String message) {
            super(message);
        }
    }
}

From: https://www.cnblogs.com/wang9192/p/3975748.html

Zhou Hongbo
  • 1,297
  • 13
  • 25