9
\$\begingroup\$

After my success with extending enum - I thought I would try to make Singleton creation as simple as possible since Java-8 makes Multitons comparatively easy.

My aim was to create a class which is a Singleton solely by the simple fact that it implements a Singleton interface. Unfortunately there is a little baggage required but it doesn't look too bad. I don't think I would use it in a real setting as it still doesn't provide the level of simplicity I am looking for but as a prototype it seems to work.

Can anyone see any ways of simplifying it further? I would like to aim for as little as possible in the TestSingleton class.

I would also like to see a way of forcing, or even pre-defining the getInstance() method but that looks impossible to me.

Here's the Multiton it uses:

/**
 * A classic Multiton making use of lambdas to delay the create.
 *
 *
 * @author OldCurmudgeon
 * @param <K> - The type that can create.
 */
public class Multiton<K extends Multiton.Creator> {

    /**
     * The storage.
     *
     * Store only Object because they must all be different types.
     */
    private final ConcurrentMap<K, Object> multitons = new ConcurrentHashMap<>();

    /**
     * The keys must be capable of creating their values.
     */
    public interface Creator {

        public abstract Object create();
    }

    /**
     * The getter.
     *
     * @param <V> - The type of the value that should be returned.
     * @param key - The unique key behind which the value is to be stored.
     * @param type - The class of the value returned.
     * @return - The value stored (and perhaps created) behind the key.
     */
    public <V> V get(final K key, Class<V> type) {
        // Has it run yet?
        Object o = multitons.get(key);
        // A null value means not yet.
        if (o == null) {
            // Use a lambda to only do the create if it is still absent.
            o = multitons.computeIfAbsent(key, k -> k.create());
        }
        return type.cast(o);
    }

    // Some simple testing.
    enum E implements Creator {

        A {

                    @Override
                    public String create() {
                        return "Face";
                    }

                },
        B {

                    @Override
                    public Integer create() {
                        return 0xFace;
                    }

                },
        C {

                    @Override
                    public Void create() {
                        return null;
                    }

                };
    }

    public static void main(String args[]) {
        try {
            Multiton<E> m = new Multiton<>();
            String face1 = m.get(E.A, String.class);
            Integer face2 = m.get(E.B, Integer.class);
            System.out.println("Face1: " + face1 + " Face2: " + Integer.toHexString(face2));
        } catch (Throwable t) {
            t.printStackTrace(System.err);
        }
    }

}

And my attempt at a interface Singleton, along with TestSingleton to demonstrate:

/**
 * Implement this and you're a singleton.
 *
 * @author OldCurmudgeon
 * @param <T> - The type we are a Singleton of.
 */
public interface Singleton<T extends Singleton<T>> {

    /**
     * A Creator command to create the singleton.
     *
     * @param <T> - The actual Singleton type.
     */
    class Creator<T extends Singleton<T>> implements Multiton.Creator {

        final Supplier<Singleton<T>> singletonConstructor;
        final Class<T> singletonClass;

        public Creator(Supplier<Singleton<T>> constructor, Class<T> clazz) {
            this.singletonConstructor = constructor;
            this.singletonClass = clazz;
        }

        @Override
        public Singleton<T> create() {
            return singletonConstructor.get();
        }

    }

    /**
     * Use one Multiton to keep track of all singletons.
     */
    static final Multiton<Creator> multitons = new Multiton<>();

    /**
     * The getInstance must create the object if need be.
     *
     * @param <T> - The type of the singleton created.
     * @param creator - A Creator for the class.
     * @return - the Singleton.
     */
    static <T extends Singleton<T>> T getInstance(Creator<T> creator) {
        return multitons.get(creator, creator.singletonClass);
    }

    /**
     * A test singleton.
     */
    class TestSingleton implements Singleton<TestSingleton> {

        /**
         * My constructor should be called only once.
         */
        public TestSingleton() {
            System.out.println("Creating a TestSingleton!");
        }
        /**
         * A Lightweight creator that can therefore be static.
         *
         * This must be static final as it is the key in the Multiton map.
         */
        private static final Creator creator = new Creator(TestSingleton::new, TestSingleton.class);

        /**
         * Implement the expected getInstance() - sadly not forced.
         * 
         * @return - The same TestSingleton for ever.
         */
        public static TestSingleton getInstance() {
            return Singleton.<TestSingleton>getInstance(creator);
        }

    }

    public static void main(String args[]) {
        try {
            TestSingleton s1 = TestSingleton.getInstance();
            TestSingleton s2 = TestSingleton.getInstance();
            if (s1 == s2) {
                System.out.println("OK!");
            }
        } catch (Throwable t) {
            t.printStackTrace(System.err);
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ Not sure what you are really tring to achieve (overcomplicated design?) but why do you if (o == null) o = multitons.computeIfAbsent()) at all? Just return the result of .computeIfAbsent(). \$\endgroup\$ – fge Nov 12 '14 at 18:05
  • \$\begingroup\$ @fge - Good point - you've picked up on a spot of premature optimisation - it comforts me to leave it there though. :) \$\endgroup\$ – OldCurmudgeon Nov 12 '14 at 21:52
  • \$\begingroup\$ This is not really premature optimization here but a "simple" question of this method's contract: it will recompute the value if the existing value does not exist or is null. In short, surrounding with a null check is redundant ;) \$\endgroup\$ – fge Nov 12 '14 at 22:02
  • \$\begingroup\$ Shouldn't the constructor of TestSingleton be private? \$\endgroup\$ – Christian Hujer Nov 26 '14 at 18:00
8
+50
\$\begingroup\$

ConcurrentMap

As was pointed out in the comments: in the Multiton.get() method:

public <V> V get(final K key, Class<V> type) {
    // Has it run yet?
    Object o = multitons.get(key);
    // A null value means not yet.
    if (o == null) {
        // Use a lambda to only do the create if it is still absent.
        o = multitons.computeIfAbsent(key, k -> k.create());
    }
    return type.cast(o);
}

There's no reason to have the double-check. The computeIfAbsent already does that:

public <V> V get(final K key, Class<V> type) {
    return type.cast(multitons.computeIfAbsent(key, k -> k.create()));
}

Generics

I have taken, and played with your code. It became apparent that there is no reason to have a generic type on the Multiton class. You only really use it as Multiton<Creator>, and that's the broadest sense of the Generics anyway. The more I played with it, though, the more I realized that your logic is in the wrong place. The right place would be to have more in the Multiton, and to get rid of the Singleton class entirely.... There is no reason for the Singleton interface. It creates a lot of ugliness anyway, because the interface cannot have private static members, so the Multiton instance needs to be non-private, making it a resource leak.

Consider the following Multiton:

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
 * A classic Multiton making use of lambdas to delay the create.
 *
 *
 * @author OldCurmudgeon
 * @param <K> - The type that can create.
 */
public class Multiton {

    /**
     * The keys must be capable of creating their values.
     */
    public interface Creator<V> {
        public Object create();
        public V cast(Object value);
    }

    public static abstract class TypedCreator<A> implements Creator<A> {
        private final Class<A> tclass;
        public TypedCreator(Class<A> tclass) {
            this.tclass = tclass;
        }

        @Override
        public final A cast(Object value) {
            return tclass.cast(value);
        }
    }

    public static final class SuppliedCreator<A> extends TypedCreator<A> {
        private final Supplier<A> supplier;
        public SuppliedCreator(Class<A> tclass, Supplier<A> supplier) {
            super(tclass);
            this.supplier = supplier;
        }

        @Override
        public Object create() {
            return supplier.get();
        }
    }

    /**
     * The storage.
     *
     * Store only Object because they must all be different types.
     */
    private final ConcurrentMap<Creator<?>, Object> multitons = new ConcurrentHashMap<>();

    /**
     * The getter.
     *
     * @param <V> - The type of the value that should be returned.
     * @param key - The unique key behind which the value is to be stored.
     * @return - The value stored (and perhaps created) behind the key.
     */
    public <V, C extends Multiton.Creator<V>> V get(final C key) {
        return key.cast(multitons.computeIfAbsent(key, k -> k.create()));
    }
}

Of particular importance, note:

  1. there's no generic type for the Multiton. This is expected. All it needs is the public get() method, and the generics for that method are determined by the call arguments.... which is point 2....
  2. the generic type of the values are declared on the Creator (which is also the key). In other words, each key knows what the type of the value should be. This means that the generic type is 'recorded' at create time, rather than retrieve time. There's no need to pass a class in to the get() call since the class is actually part of the Creator.
  3. I created 2 'helper' classes that are Creator instances. These classes TypedCreator and SuppliedCreator are simpler ways to actually create anonymous, or Java-8 based Creators.

Note that I can't see a convenient way to use a Singleton interface. The interface depends on a shared instance of the Multiton, so as a result, there needs to be some static data in the Singleton. As a consequence, an interface is the wrong language structure to use. An abstract class may be better. Still, the usage examples for the Multiton above are simple (in some common 'utility' class, or somewhere shared):

public static final Multiton MULTITON = new Multiton();

Then, whenever you have a need for a Singleton:

public static final Multiton.Creator<String> HARDWORK = new Multiton.SuppliedCreator<>(String.class, () -> initializeHardWork());

and then:

String hardwork = MULTITON.get(HARDWORK);

This can be encapsulated in to an abstract Singleton class as:

public abstract class Singleton {
    protected static final Multiton MULTITON = new Multiton();        
}

The Singleton class can then be used in various ways, here's an example:

public class TestSingleton extends Singleton {

    private static final Multiton.Creator<TestSingleton> KEY = new Multiton.SuppliedCreator<>(TestSingleton.class, TestSingleton::new);

    public static final TestSingleton getInstance() {
        return MULTITON.get(KEY);
    }

    private final String name;

    private TestSingleton() {

        try {
            Thread.sleep(200);
            name = "Instance " + System.currentTimeMillis();
            System.out.println("Slow Instance: " + name);
            Thread.sleep(200);
        } catch (InterruptedException e) {
            throw new IllegalStateException("Interrupted ", e);
        }
    }

    @Override
    public String toString() {
        return name;
    }

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(8);
        for (int i = 0; i < 20; i++) {
            service.execute(() -> {System.out.println(TestSingleton.getInstance());});
        }
    }

}
\$\endgroup\$
  • \$\begingroup\$ That looks awesome! I need to absorb this a little before commenting in detail. \$\endgroup\$ – OldCurmudgeon Dec 1 '14 at 23:55
  • \$\begingroup\$ This is really nice - I think it's as minimal as we can get right now but I wish we could get further. Re: ConcurrentMap - Agreed. Re: Generics - I had (kind of) come to the same conclusion but not quite with the clarity you have used. I have to admit the elegance of the final result is a huge plus. We now have quite a simple ASingleton extends Singleton class with the static creator and a one-liner getInstance. Obviously we also need the private constructor too which you are missing. The static final MULTITON in Singleton works nicely. Is the <T> a mistake? It is not used. \$\endgroup\$ – OldCurmudgeon Dec 3 '14 at 10:42
  • \$\begingroup\$ The changes you have made look fine... the <T> on the Singleton is an old version that I forgot to edit in this answer. Pleased I could help. \$\endgroup\$ – rolfl Dec 3 '14 at 11:22
  • \$\begingroup\$ Removed the errant <T> from my answer, and much appreciated the bounty, thanks. \$\endgroup\$ – rolfl Dec 4 '14 at 22:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.