Singletons with Initialization Methods

Introduction to Singletons

The singleton design pattern is a staple in introductory software engineering courses. This design pattern stipulates that, for some class, only a single instance of the class (that is, one object) can be created. In Java, this design pattern is realized by making the constructor private, and ensuring that any access to the one instance of that class goes through a zero-parameter, static accessor method.

Back when I was a student learning about the singleton design pattern, two questions in particular bothered me. First, how can we deal with any exceptions that get thrown while the singleton instance is being created? And second, how can we pass some sort of parameter (like a filename) to the initialization of the one instance of the singleton class?

In today’s blog post, we’ll explore the first of these two questions in the context of a singleton class that represents the global configuration of an application. We’ll show how this question can be addressed through the use of a special initialization method, together with the Java standard IllegalStateException class. Our solution to the first question will also hint at a solution to the second question, which will be left as an exercise for the reader.

A Motivating Example

Let’s set the stage for today’s problem by working backwards from a conceptual design. Let’s start with a singleton class that holds the configuration settings for an entire graphical application. One of the settings we can retrieve from the singleton is the application’s text colour:

public class AppConfiguration
{
    private static AppConfiguration instance;

    private AppConfiguration()
    {
        ...
    }

    public static AppConfiguration getInstance()
    {
        if (instance == null) {
            instance = new AppConfiguration();
        }
        return instance;
    }

    public Color getTextColour()
    {
        return ...
    }
}

As a design consideration, let’s assume that the application has to read its global configuration settings from a configuration file.

In order to ensure that the configuration file has been read before AppConfiguration.getTextColour() is called, we could read the configuration file in the constructor for AppConfiguration:

public class AppConfiguration
{
    private static final String CONF_FILE =
        "/etc/vcstutoring.conf";
    private static AppConfiguration instance;

    private AppConfiguration()
    {
        readConfigFile(CONF_FILE);
        ...
    }

    public static AppConfiguration getInstance()
    {
        if (instance == null) {
            instance = new AppConfiguration();
        }
        return instance;
    }

    public Color getTextColour()
    {
        return ...
    }

    private void readConfigFile(String filename)
    {
        // Critical detail missing here
        ...
    }
}

There’s one significant oversight in the example above, though. Presumably, the AppConfiguration.readConfigFile(String) method can throw an IOException, if reading the configuration file fails. Working up the stack, that means that the AppConfiguration constructor and the AppConfiguration.getInstance() method can also throw an IOException:

public class AppConfiguration
{
    private static final String CONF_FILE =
        "/etc/vcstutoring.conf";
    private static AppConfiguration instance;

    private AppConfiguration()
        throws IOException
    {
        readConfigFile(CONF_FILE);
        ...
    }

    public static AppConfiguration getInstance()
        throws IOException
    {
        if (instance == null) {
            instance = new AppConfiguration();
        }
        return instance;
    }

    public Color getTextColour()
    {
        return ...
    }

    private void readConfigFile(String filename)
        throws IOException
    {
        // Note: throws IOException
        ...
    }
}

AppConfiguration.getInstance() throwing an IOException creates a software design mess, because every time calling code uses the AppConfiguration instance, it has to deal with a potential IOException:

public class TextWindow
{
    public void display()
    {
        AppConfiguration appConf;
        Color textCol;
        try {
            appConf = AppConfiguration.getInstance();
        }
        catch (IOException ioe) {
            // ???
        }
        textCol = appConf.getTextColour();
        ...
    }
}

Not only is this code difficult to read, but it’s not even clear what our application should do if it catches an IOException from attempting to read the configuration file in the middle of displaying a window of text. Should it skip setting the text colour? Exit the application with an error?

Ideally, the AppConfiguration constructor, and its static accessor method, wouldn’t throw IOExceptions at all.

Adding an Initialization Method

One design approach to address exceptions being thrown during a singleton’s initialization is the use of a special initialization method. Before the singleton is used meaningfully by calling code, the initialization method has to be called. Let’s modify the previous AppConfiguration design to use an initialization method named AppConfiguration.init():

public class AppConfiguration
{
    private static final String CONF_FILE =
        "/etc/vcstutoring.conf";
    private static AppConfiguration instance;

    private AppConfiguration()
    {
        // Note: no IOException
        ...
    }

    public static AppConfiguration getInstance()
    {
        // Note: no IOException
        if (instance == null) {
            instance = new AppConfiguration();
        }
        return instance;
    }

    public void init()
        throws IOException
    {
        readConfigFile(CONF_FILE);
        ...
    }

    public Color getTextColour()
    {
        return ...
    }

    private void readConfigFile(String filename)
        throws IOException
    {
        ...
    }
}

Our main application code now has to call the AppConfiguration.init() method somewhere near the start of the application:

public class VCSApplication
{
    public static void main(String[] args)
    {
        AppConfiguration appConf =
            AppConfiguration.getInstance();
        try {
            appConf.init();
        }
        catch (IOException ioe) {
            // Deal with exception
        }
        TextWindow appWindow = ...
    }
}

Now, an IOException only has to be caught in a single place: where AppConfiguration.init() is called. Not only does this approach simplify our code, but it also places catching an IOException related to the configuration file where it belongs: right at the application’s launch.

We should quickly note that there’s nothing special about the method name init(). The method could equally have been called initialize() or loadConfiguration().

Using Exceptions to Ensure Initialization

Having moved the initialization of the singleton object into a separate AppConfiguration.init() method, let’s harden our code to deal with a potential programming error. Namely, what should we do if AppConfiguration.getTextColour() is called before AppConfiguration.init()?

The answer is to use the Java standard IllegalStateException class. From the Oracle Java 17 API documents, the purpose of this class is to signal “that a method has been invoked at an illegal or inappropriate time”.

One time when it’s illegal to call AppConfiguration.getTextColour() is if the configuration file hasn’t been read yet, because AppConfiguration.init() hasn’t been called.

Let’s add a single boolean instance variable to the AppConfiguration singleton, so it can track whether it’s been initialized. If not, the singleton object will throw an IllegalStateException from AppConfiguration.getTextColour() to indicate that the method can’t be called yet.

In the following example, we call the instance variable initCalled, and it only gets set to true at the end of the AppConfiguration.init() method:

public class AppConfiguration
{
    private static final String CONF_FILE =
        "/etc/vcstutoring.conf";
    private static AppConfiguration instance;

    private boolean initCalled;

    private AppConfiguration()
    {
        initCalled = false;
        ...
    }

    public static AppConfiguration getInstance()
    {
        if (instance == null) {
            instance = new AppConfiguration();
        }
        return instance;
    }

    public void init()
        throws IOException
    {
        readConfigFile(CONF_FILE);
        ...
        initCalled = true;
    }

    public Color getTextColour()
    {
        if (initCalled == false) {
            throw new IllegalStateException("AppConfiguration not initialized");
        }
        return ...
    }

    private void readConfigFile(String filename)
        throws IOException
    {
        ...
    }
}

Because an IllegalStateException is an unchecked RuntimeException — the kind of Exception appropriate for catching programming errors — calling code doesn’t need to use a try/catch block around the AppConfiguration.getTextColour() method:

public class TextWindow
{
    public void display()
    {
        AppConfiguration appConf =
            AppConfiguration.getInstance();
        Color textCol =
            appConf.getTextColour();
        ...
    }
}

With AppConfiguration.getTextColour() verifying that AppConfiguration.init() has been called, we’re preemptively guarding against any programming errors in the rest of our code that could cause us to call AppConfiguration.getTextColour() before the application’s configuration file is loaded.

Protecting Against Double-Initialization

To further build on the use of IllegalStateExceptions, we can also protect against the AppConfiguration.init() method being called more than once. Whether we should include this sort of code-hardening depends on the use case of the singleton class. For our example of an application-wide collection of configuration settings, it’s likely that we only want to read the configuration file once.

Relying on the same initCalled instance variable, we can ensure AppConfiguration.init() is only called one time, throwing an IllegalStateException otherwise:

public void init()
    throws IOException
{
    if (initCalled) {
        throw new IllegalStateException("AppConfiguration already initialized");
    }
    readConfigFile(CONF_FILE);
    ...
    initCalled = true;
}

With checks on initCalled in both AppConfiguration.init() and AppConfiguration.getTextColour(), we’re guaranteed that the application’s configuration file has been read once and only once before any configuration parameters are used by the rest of the application.

Exercise

Another challenge that the singleton design pattern can raise is: how can we parameterize the initialization of a singleton? The solution to this problem is very similar to how we can deal with exceptions being thrown during singleton initialization — a special initialization method.

Let’s consider a variation of our application where the name of the configuration file is provided as a command-line parameter, instead of being hard-coded into the AppConfiguration class.

How could we initialize the AppConfiguration singleton instance if our application starts like the following?

public class VCSApplication
{
    public static void main(String[] args)
    {
        String confFilename = getConfigFilename(args);
        // Now, initialize the AppConfiguration instance
        ...
    }

    private static String getConfigFilename(String[] args)
    {
        return args[0];
    }
}

I encourage students to rewrite the AppConfiguration class to remove the hardcoded AppConfiguration.CONF_FILE constant. Instead, use the confFilename String in the VCSApplication.main(String[]) method as the name of the configuration file to read.

Hint: while the use of a special initialization method is a key step in answering this problem, the signature of the AppConfiguration.init() method will have to change.

Summary

The singleton design pattern is an elegant solution to a situation where our program needs just one globally accessible instance of a class. By making the constructor of the class private, we can ensure that no instance of the singleton class is created outside of the class itself. The class then in turn controls access to that single instance through a static accessor method.

But, how should we deal with exceptions that can be thrown when the single instance of the class is created? In today’s blog post, we looked at this problem in the context of a singleton that has to read an application-scale configuration file — a process that can throw checked I/O exceptions. (Similar I/O exceptions could be thrown by a singleton that controls access to a log file, or by a singleton driver for a printer.)

We demonstrated that in this context, a special initialization method can be invoked on the singleton instance near the start of the application. Calling the initialization method a single time early in the program’s execution allows us to deal with any potential exceptions in that one location. Future calling code (that gets the application’s text colour, in our example) doesn’t have to worry about dealing with any exceptional conditions, resulting in cleaner code that’s easier to read and maintain.

For more tips, and to arrange for personalized tutoring for yourself or your study group, check out Vancouver Computer Science Tutoring.