'. '

Injectable Singleton

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(Injectable Meta-Singleton)
(Testability)
(12 intermediate revisions not shown.)
Line 1: Line 1:
-
[[Singleton]]s are sometimes dishonest as a design anti-pattern. Right, one may use them improperly, but with guidance of proper methodology there is nothing wrong on [[singleton]]s. This page provides step by step cook book for usage of so called [[Injected Singleton]]s.
+
[[Singleton]]s are sometimes dishonest as a design anti-pattern. True, one may use them improperly. But with guidance of proper methodology there is nothing wrong on [[singleton]]s. This page provides step by step cook book for usage of so called [[Injectable Singleton]]s.
== What this is not ==
== What this is not ==
-
We are going to introduce an enhanced version of the [[singleton]] pattern that supports [[testability]], provides smooth [[Convention over Configuration]] and [[Component Injection]]. It can be seen as nice alternative companion to ''application context'' used by [[dependency injection]].
+
We are going to introduce an enhanced version of the [[singleton]] pattern that supports [[testability]], provides smooth [[Convention over Configuration]] and [[Component Injection]]. It can be seen as nice alternative companion to ''application context'' used by [[dependency injection]]. However when talking about [[singleton]] pattern, don't imagine [[Java]]'s '''SecurityManager''' or '''URLStreamHandlerFactory'''. Those are not [[Injectable Singleton]]s, they require non-trivial amount of initialization code and basically let your application knowledge leak out through out the system.
-
 
+
-
However when talking about [[singleton]] pattern, don't imagine [[Java]]'s '''SecurityManager''' or '''URLStreamHandlerFactory'''. Those are not [[Injectable Singleton]]s, they require non-trivial amount of initialization code and basically let your application knowledge leak out through out the system.
+
== Defining the [[Singleton]] ==
== Defining the [[Singleton]] ==
-
Imagine you want to create a [[singleton]] service to display questions to the user. This would be your [[API]]:
+
Let start with as simple example as possible. Imagine a [[singleton]] with one useful method. For example let's pretend that you want to create a service [[singleton]] to display questions to the user. This is how your [[API]] can look like:
<source lang="java" snippet="singletons.injectable.api"/>
<source lang="java" snippet="singletons.injectable.api"/>
-
Slightly surprising thing is that the class is abstract (e.g. it contains no implementation) and to allow extensibility it provides protected constructor. This is not what traditional [[singleton]]s usually do, but remember this is an [[Injectable Singleton]]!
+
At first glance, there is one slightly surprising thing. The class is abstract (e.g. it contains no implementation) and it provides protected constructor. This is not what traditional [[singleton]]s usually do, but remember this is an [[Injectable Singleton]]! We need to open up and allow various implementations to be [[injection|injected]] into the [[singleton]]'s slot.
-
As the important motto of [[Injectable Singleton]]s is [[Convention over Configuration]] each such [[singleton]] needs to define default (even dummy) implementation. Let's do it like this:
+
However as one of the important mottos of [[Injectable Singleton]]s is [[Convention over Configuration]], we need to provide the ''convention''. How that shall be done? Each [[Injectable Singleton]] needs to define default (even dummy) implementation! Let's do it like this:
<source lang="java" snippet="singletons.injectable.dummyimpl"/>
<source lang="java" snippet="singletons.injectable.dummyimpl"/>
Line 21: Line 19:
In this case, the implementation of the '''yesOrNo''' method is really trivial and not very useful, but often there are cases when the default behavior can be made acceptable for many purposes.
In this case, the implementation of the '''yesOrNo''' method is really trivial and not very useful, but often there are cases when the default behavior can be made acceptable for many purposes.
-
Now the time as come to write proper initialization code (that will support [[Component Injection|extensibility]]). We can do it either in [[Java]]6 standard way via [[ServiceLoader]]:
+
=== Initialize ===
 +
 
 +
Now the time has come to write proper initialization code (that will support [[Component Injection|extensibility]]). We can either stick with plain [[Java]]6 standard and use [[ServiceLoader]]:
<source lang="java" snippet="singletons.injectable.serviceloader"/>
<source lang="java" snippet="singletons.injectable.serviceloader"/>
-
Or using small [[Lookup]] library (we'll see later what benefit it provides):
+
Or we can use small [[Lookup]] library (we'll see later what benefit it provides):
<source lang="java" snippet="singletons.injectable.lookup"/>
<source lang="java" snippet="singletons.injectable.lookup"/>
Line 33: Line 33:
== Usage ==
== Usage ==
-
To use a [[singleton]], it is enough to import its [[API]] class and call its methods. Because [[singleton]]s are ''inherently initialized'' - e.g. as soon as one calls their static getter, one gets initialized instance - there is no need for any special configuration in the client code. Just:
+
To use a [[singleton]], it is enough to import its [[API]] class and call its methods. Because [[singleton]]s are ''inherently initialized'' - e.g. as soon as one calls their static getter, one gets some initialized instance - there is no need for any special configuration in the client code. Just call:
<source lang="java" snippet="singletons.injectable.usage"/>
<source lang="java" snippet="singletons.injectable.usage"/>
-
This makes the whole use as simple as possible by following half of the [[Convention over Configuration]] motto: ''If you are OK with default implementation, just use it''. As soon as you link with the [[API]] class, you can be sure that you'll get reasonable implementation. No other configuration is needed. This is the output of such sample execution:
+
This makes the whole use as simple as possible by following the first half of the [[Convention over Configuration]] motto: ''If you are OK with default implementation, just use it''. As soon as a client code links with the [[API]] class, it can be sure that it will get reasonable implementation. No other configuration is needed. This is the output of such sample execution (which includes just the client code and the [[singleton]]'s [[API]] with default implementation):
<source lang="bash">
<source lang="bash">
Line 47: Line 47:
== Configure ==
== Configure ==
-
Of course, using the dummy implementation is not always optimal. As our systems get more and more [[Modularity|assembled at deploy time]] there especially needs to be a way to support easy configuration during application deployment. This is inherently supported due to adherence to [[ServiceLoader|Java Standard Extension Mechanism]]. Any time later, in any additional project that produces completely independent [[JAR]] file one can compile following piece of code:
+
Of course, using the dummy implementation is not always optimal. Complex systems may require various customizations and may need to inject such advanced implementations into the [[singleton]] slots.
 +
 
 +
Also, as our systems get more and more [[Modularity|assembled at deploy time]], there is a especial need to support easy configuration during application deployment (or assembly), preferably without any coding (in [[Java]] or [[XML]]). This is inherently supported due to adherence to [[ServiceLoader|Java Standard Extension Mechanism]]. Any developer can create a completely different [[JAR]] file,which contains result of compilation of following piece of code:
<source lang="java" snippet="singletons.injectable.implement"/>
<source lang="java" snippet="singletons.injectable.implement"/>
-
The [[JAR]] containing this class (and the generated ''META-INF/services/'' entry, as generated by processing ''@ServiceProvider'' annotation via compile time [[AnnotationProcessor]] or alternatively created manually) can be put onto the application classpath. As soon as you do
+
Such [[JAR]] file (containing the .class and the associated ''META-INF/services/'' entry, as generated by processing ''@ServiceProvider'' annotation via compile time [[AnnotationProcessor]] or alternatively created manually) can later be put onto the application classpath. As soon as you include it:
<source lang="bash">
<source lang="bash">
-
$ java -jar DisplayerMain.jar -cp Displayer.jar:DefaultDisplayerImpl.jar
+
$ java -jar DisplayerMain.jar -cp Displayer.jar:SwingDisplayerImpl.jar
</source>
</source>
-
your system will get properly configured and will use the injected enhanced [[singleton]]:
+
your system will read the ''META-INF/services/'' entry and configure its [[singleton]] automatically. The configuration during application assembly is a matter of simply setting up application classpath!
[[Image:Singleton.png]]
[[Image:Singleton.png]]
-
Don't waste time writing configuration files! All you need to do to configure [[Injectable Singleton]]s is to include correct [[JAR]]s on application classpath. Follow the second part of the [[Convention over Configuration]] motto. Let application assemblers ''dissatisfied with default, drop-in their implementations to classpath''!
+
Don't waste time writing configuration files! All you need to do to configure [[Injectable Singleton]]s is to include correct [[JAR]]s on application classpath. Follow the second part of the [[Convention over Configuration]] motto. Let application assemblers which are ''dissatisfied with default'', drop-in real implementations onto the classpath!
== [[Testability]] ==
== [[Testability]] ==
-
Of course, these days one just has to write code that is inherently testable. One of the critiques of [[singleton]] pattern was that it does not allow this. That it is not possible to ''mock'' in different [[singleton]] behavior during test execution. [[Injectable Singleton]]s are here to allow you to do exactly this:
+
Of course, these days one just has to write code that is inherently testable. Critics of regular [[singleton]] pattern accuse singletons from not being testable. That it is not possible to ''mock'' in different [[singleton]] behavior during test execution. [[Injectable Singleton]]s are here to allow you to do exactly this:
<source lang="java" snippet="singletons.injectable.test"/>
<source lang="java" snippet="singletons.injectable.test"/>
-
The first thing the test does, is to [[inject]] its own, ''mock'' implementation of [[singleton]] via utility class ''MockServices''. Then the whole test execution is using this instance, which is obviously enhanced to allow easy [[testability]].
+
During the initial setup the test [[inject]]s its own ''mock'' implementation of the tested [[singleton]] via testing utility class ''MockServices''. Then the whole test execution is using such ''mock'' instance, which is obviously enhanced to allow easy [[testability]].
-
I shall however mention an important restriction. Because [[Injected Singleton]]s support class-level [[co-existence]], each set of tests that get linked together share the same [[singleton]]s. This may not be always appropriate - in such case split your test into two classes and make sure they get executed in a separate [[VM]].
+
I shall however mention one important restriction. Because [[Injectable Singleton]]s support class-level [[co-existence]], each set of tests that links together share the same [[singleton]]s. This may not be always appropriate. Sometimes one wants different ''mock'' for different tests. However the cure is simple: split your tests into two classes and make sure each gets executed in a separate [[VM]].
== Injectable Meta-Singleton ==
== Injectable Meta-Singleton ==
-
As mentioned earlier, there are two recommended ways to configure the [[Injectable Singleton]]s. You can rely on [[Java]]'s standard [[ServiceLoader]] or you can choose [[Lookup]] library. Of course, it is often preferable to have as little dependencies as possible (and thus the [[ServiceLoader]] shall be natural choice), however the [[Lookup]] library offers certain advantages that make it attractive. Most of them are listed [[Lookup|elsewhere]], but let me point your attention to one, relevant for topic of this page.
+
As mentioned earlier, there are two recommended ways to configure the [[Injectable Singleton]]s. You can rely on [[Java]]'s standard [[ServiceLoader]] or you can choose [[Lookup]] library. Of course, it is often preferable to have as little dependencies as possible (and thus the [[ServiceLoader]] shall be natural choice), however the [[Lookup]] library offers certain advantages that make it attractive as well. Most of them are listed [[Lookup|elsewhere]], but let me point your attention to one, relevant for topic of this page.
-
[[Lookup]] is a [[singleton]]. In fact it is an [[Injectable singleton]]! There is a ''Lookup.getDefault()'' method which returns a ''meta''-factory for all [[Injectable Singleton]]s, and this factory is also [[injection|injectable]]. As a result (in contrary to [[ServiceLoader]]) one can completely customize the way all [[Injectable singleton]]s in a system are discovered and created.
+
[[Lookup]] offers a [[singleton]]. In fact it provides an [[Injectable singleton]]! There is a ''Lookup.getDefault()'' method which returns a ''meta''-factory for all [[Injectable Singleton]]s, and this factory is also [[injection|injectable]]. As a result (in contrary to [[ServiceLoader]] where the default behavior is hard-coded and cannot be [[injection|injected]]) one can completely customize how all the [[Injectable singleton]]s in a system are discovered and created.
-
One can even use [[SpringAndLookup]] bridge, register [[Lookup]] that wraps a [[Spring]]'s [[XML]] configuration file and then one can configure whole application using classical [[Dependency Injection]] tricks (I can provide an executable demo, if there is an interest; just [[talk:Injectable Singleton|leave a note]]).
+
One can even use [[LookupAndSpring]] bridge, register [[Lookup]] that wraps [[Spring]]'s [[XML]] configuration file as the default meta-[[singleton]]. Then one can configure the whole application using classical [[Dependency Injection]] tricks (I can provide an executable demo, if there is an interest; just [[talk:Injectable Singleton|leave a note]]).
== Polemic ==
== Polemic ==
Line 85: Line 87:
One of the critics mentioned that this is not really a [[singleton]], as it allows multiple instances to be created. Yes, people can subclass the abstract class and create as many instances as they want. However whoever uses just the static [[API]] getter, is guaranteed to receive only one, shared instance. For those who use the [[ClientAPI]] part of such [[singleton]], the whole things appears to be [[singleton]]. That is why this worry is more about ''beauty'' than real world problems.
One of the critics mentioned that this is not really a [[singleton]], as it allows multiple instances to be created. Yes, people can subclass the abstract class and create as many instances as they want. However whoever uses just the static [[API]] getter, is guaranteed to receive only one, shared instance. For those who use the [[ClientAPI]] part of such [[singleton]], the whole things appears to be [[singleton]]. That is why this worry is more about ''beauty'' than real world problems.
-
TBD:
+
Also, the [[Injectable Singleton]]s as presented so far, are violating all the suggestions to [[Separate APIs for Clients and Providers]]. This may or may not matter. It is probably OK to use subclassable abstract classes for [[API]] that is from 99% called and only rarely implemented. This is often cause of [[singleton]]s - there is one instance and many callers. Thus violating the best practice may be acceptable (in [[NetBeans]] we have a lot of such [[singleton]]'s, ''WindowManager'', ''DialogDisplayer'', ''ExecutionEngine'' and we never faced much problems due to that - there are usually just three implementations of those - one default, one real, one for tests).
-
* [[Separate APIs for Clients and Providers]]
+
 
 +
In case the dual nature of these [[Injectable Singleton]]s shall be a problem, the fix is simple. Just make all their methods ''protected abstract'' (as advocated in [[ClarityOfAccessModifiers]]). Then it will be clear that the [[singleton]] is here to be [[injection|injected]] and not called (no [[API]] client can call ''protected'' methods). Then, of course, you need to provide some other ''public'' methods that [[ClientAPI]] users can call. That is more verbose, but it clearly separates the concerns: There is a well defined [[API]] for clients as well as providers.
 +
 
 +
 
 +
Enjoy [[singleton]]s, make them [[Injectable Singleton|injectable]]!
 +
 
 +
<comments/>
 +
 
 +
 
 +
[[Category:APIDesignPatterns]] [[Category:APIDesignPatterns:Creational]] [[Category:APIDesignPatterns:Meta]]

Revision as of 07:25, 12 October 2021

Singletons are sometimes dishonest as a design anti-pattern. True, one may use them improperly. But with guidance of proper methodology there is nothing wrong on singletons. This page provides step by step cook book for usage of so called Injectable Singletons.

Contents

What this is not

We are going to introduce an enhanced version of the singleton pattern that supports testability, provides smooth Convention over Configuration and Component Injection. It can be seen as nice alternative companion to application context used by dependency injection. However when talking about singleton pattern, don't imagine Java's SecurityManager or URLStreamHandlerFactory. Those are not Injectable Singletons, they require non-trivial amount of initialization code and basically let your application knowledge leak out through out the system.

Defining the Singleton

Let start with as simple example as possible. Imagine a singleton with one useful method. For example let's pretend that you want to create a service singleton to display questions to the user. This is how your API can look like:

Code from DialogDisplayer.java:
See the whole file.

public abstract class DialogDisplayer {
    protected DialogDisplayer() {
    }
 
    /** Ask user a question.
     *
     * @param query the text of the question
     * @return true if user confirmed or false if declined
     */
    public abstract boolean yesOrNo(String query);
 
    public static DialogDisplayer getDefault() {
        return Impl.DEFAULT;
    }
}
 

At first glance, there is one slightly surprising thing. The class is abstract (e.g. it contains no implementation) and it provides protected constructor. This is not what traditional singletons usually do, but remember this is an Injectable Singleton! We need to open up and allow various implementations to be injected into the singleton's slot.

However as one of the important mottos of Injectable Singletons is Convention over Configuration, we need to provide the convention. How that shall be done? Each Injectable Singleton needs to define default (even dummy) implementation! Let's do it like this:

Code from DialogDisplayer.java:
See the whole file.

private static final class Impl extends DialogDisplayer {
    private static final DialogDisplayer DEFAULT = initialize();
 
    @Override
    public boolean yesOrNo(String query) {
        System.err.printf("Saying no to '%s'\n", query);
        return false;
    }
}
 

In this case, the implementation of the yesOrNo method is really trivial and not very useful, but often there are cases when the default behavior can be made acceptable for many purposes.

Initialize

Now the time has come to write proper initialization code (that will support extensibility). We can either stick with plain Java6 standard and use ServiceLoader:

Code from DialogDisplayer.java:
See the whole file.

private static DialogDisplayer initializeServiceLoader() {
    Iterator<DialogDisplayer> it = null;
    it = ServiceLoader.load(DialogDisplayer.class).iterator();
    return it != null && it.hasNext() ? it.next() : new Impl();
}
 

Or we can use small Lookup library (we'll see later what benefit it provides):

Code from DialogDisplayer.java:
See the whole file.

private static DialogDisplayer initializeLookup() {
    final Lookup lkp = Lookup.getDefault();
    DialogDisplayer def = lkp.lookup(DialogDisplayer.class);
    return def != null ? def : new Impl();
}
 

And that is all. Our first, Injectable Singleton is ready for being used.

Usage

To use a singleton, it is enough to import its API class and call its methods. Because singletons are inherently initialized - e.g. as soon as one calls their static getter, one gets some initialized instance - there is no need for any special configuration in the client code. Just call:

Code from Main.java:
See the whole file.

import org.apidesign.singletons.api.DialogDisplayer;
 
public class Main {
    public static void main(String[] args) {
        if (DialogDisplayer.getDefault().yesOrNo(
            "Do you like singletons?"
        )) {
            System.err.println("OK, thank you!");
        } else {
            System.err.println(
                "Visit http://singletons.apidesign.org to"
                + " change your mind!"
            );
        }
    }
}
 

This makes the whole use as simple as possible by following the first half of the Convention over Configuration motto: If you are OK with default implementation, just use it. As soon as a client code links with the API class, it can be sure that it will get reasonable implementation. No other configuration is needed. This is the output of such sample execution (which includes just the client code and the singleton's API with default implementation):

$ java -jar DisplayerMain.jar -cp Displayer.jar
Saying no to 'Do you like singletons?'
Visit http://singletons.apidesign.org to change your mind!

Configure

Of course, using the dummy implementation is not always optimal. Complex systems may require various customizations and may need to inject such advanced implementations into the singleton slots.

Also, as our systems get more and more assembled at deploy time, there is a especial need to support easy configuration during application deployment (or assembly), preferably without any coding (in Java or XML). This is inherently supported due to adherence to Java Standard Extension Mechanism. Any developer can create a completely different JAR file,which contains result of compilation of following piece of code:

Code from SwingDialogDisplayer.java:
See the whole file.

@ServiceProvider(service=DialogDisplayer.class)
public final class SwingDialogDisplayer extends DialogDisplayer {
    @Override
    public boolean yesOrNo(String query) {
        final int res = JOptionPane.showConfirmDialog(null, query);
        return res == JOptionPane.OK_OPTION;
    }
}
 

Such JAR file (containing the .class and the associated META-INF/services/ entry, as generated by processing @ServiceProvider annotation via compile time AnnotationProcessor or alternatively created manually) can later be put onto the application classpath. As soon as you include it:

$ java -jar DisplayerMain.jar -cp Displayer.jar:SwingDisplayerImpl.jar

your system will read the META-INF/services/ entry and configure its singleton automatically. The configuration during application assembly is a matter of simply setting up application classpath!

Image:Singleton.png

Don't waste time writing configuration files! All you need to do to configure Injectable Singletons is to include correct JARs on application classpath. Follow the second part of the Convention over Configuration motto. Let application assemblers which are dissatisfied with default, drop-in real implementations onto the classpath!

Testability

Of course, these days one just has to write code that is inherently testable. Critics of regular singleton pattern accuse singletons from not being testable. That it is not possible to mock in different singleton behavior during test execution. Injectable Singletons are here to allow you to do exactly this:

Code from MainTest.java:
See the whole file.

public class MainTest {
    @BeforeClass
    public static void setUpClass() throws Exception {
        MockServices.setServices(MockDialogDisplayer.class);
    }
 
    @Test
    public void testMainAsksAQuestion() {
        assertNull(
            "No question asked yet", MockDialogDisplayer.askedQuery
        );
        Main.main(new String[0]);
        assertNotNull(
            "main code asked our Mock displayer",
            MockDialogDisplayer.askedQuery
        );
    }
 
    public static final class MockDialogDisplayer extends DialogDisplayer {
        static String askedQuery;
 
        @Override
        public boolean yesOrNo(String query) {
            askedQuery = query;
            return false;
        }
    }
}
 

During the initial setup the test injects its own mock implementation of the tested singleton via testing utility class MockServices. Then the whole test execution is using such mock instance, which is obviously enhanced to allow easy testability.

I shall however mention one important restriction. Because Injectable Singletons support class-level co-existence, each set of tests that links together share the same singletons. This may not be always appropriate. Sometimes one wants different mock for different tests. However the cure is simple: split your tests into two classes and make sure each gets executed in a separate VM.

Injectable Meta-Singleton

As mentioned earlier, there are two recommended ways to configure the Injectable Singletons. You can rely on Java's standard ServiceLoader or you can choose Lookup library. Of course, it is often preferable to have as little dependencies as possible (and thus the ServiceLoader shall be natural choice), however the Lookup library offers certain advantages that make it attractive as well. Most of them are listed elsewhere, but let me point your attention to one, relevant for topic of this page.

Lookup offers a singleton. In fact it provides an Injectable singleton! There is a Lookup.getDefault() method which returns a meta-factory for all Injectable Singletons, and this factory is also injectable. As a result (in contrary to ServiceLoader where the default behavior is hard-coded and cannot be injected) one can completely customize how all the Injectable singletons in a system are discovered and created.

One can even use LookupAndSpring bridge, register Lookup that wraps Spring's XML configuration file as the default meta-singleton. Then one can configure the whole application using classical Dependency Injection tricks (I can provide an executable demo, if there is an interest; just leave a note).

Polemic

One of the critics mentioned that this is not really a singleton, as it allows multiple instances to be created. Yes, people can subclass the abstract class and create as many instances as they want. However whoever uses just the static API getter, is guaranteed to receive only one, shared instance. For those who use the ClientAPI part of such singleton, the whole things appears to be singleton. That is why this worry is more about beauty than real world problems.

Also, the Injectable Singletons as presented so far, are violating all the suggestions to Separate APIs for Clients and Providers. This may or may not matter. It is probably OK to use subclassable abstract classes for API that is from 99% called and only rarely implemented. This is often cause of singletons - there is one instance and many callers. Thus violating the best practice may be acceptable (in NetBeans we have a lot of such singleton's, WindowManager, DialogDisplayer, ExecutionEngine and we never faced much problems due to that - there are usually just three implementations of those - one default, one real, one for tests).

In case the dual nature of these Injectable Singletons shall be a problem, the fix is simple. Just make all their methods protected abstract (as advocated in ClarityOfAccessModifiers). Then it will be clear that the singleton is here to be injected and not called (no API client can call protected methods). Then, of course, you need to provide some other public methods that ClientAPI users can call. That is more verbose, but it clearly separates the concerns: There is a well defined API for clients as well as providers.


Enjoy singletons, make them injectable!

<comments/>

Personal tools
buy