While one of the main ideas behind Object Oriented programming is to be able to create multiple, and different instances of the same class, this is not always convenient.

If you have a class representing users of a system, we probably want to be able to create several instances, but in most applications there should be only one set of configuration at any given time.

The Singleton Pattern offers the theory behind the solution.

Let's start with a very simple example using the MooX::Singleton extension of Moo.

We have the MyConf.pm file with the following content:

examples/singleton-moo/MyConf.pm

package MyConf;
use Moo;
with 'MooX::Singleton';
 
has file => (is => 'ro', required => 1);
 
1;

For now it only receives a filename, the name of the configuration file it is supposed to read, but to keep the example small we don't actually read the file.

Our "application" code looks like this:

examples/singleton-moo/application.pl

use strict;
use warnings;
use 5.010;
 
use MyConf;
 
my $c = MyConf->instance( file => 'conf.ini' );
say $c->file;
 
my $d = MyConf->instance( file => 'data.ini' );
say $d->file;
 
my $w = MyConf->instance();
say $w->file;
 
my $o = MyConf->new( file => 'other.ini' );
say $o->file;
 
my $z = MyConf->new( file => 'zorg.ini' );
say $z->file;

Instead of calling new as the constructor, we must call the instance method. For the first call it behaves exactly as the new method would, but when we call it again it disregards the parameters and returns the exact same object as earlier.

Calling new does not have any special behavior. The output of the above script will be:

conf.ini
conf.ini
conf.ini
other.ini
zorg.ini

Please note, the instance method will silently ignore any arguments passed in the second or any subsequent call. This can cause surprises, so I'd recommend passing arguments only in one place of the application and calling instance without any arguments in other places.

Why is this singleton interesting?

The above example is probably not showing why is using a singleton interesting.

What if we have an application implemented in 10 different modules, all of which need access to the configuration information?

One solution would be to create an instance of the MyConf class in the main script and then pass that object to the constructor of each class. Then each class would have an attribute called conf. This is doable, but it requires a lot of parameter passing.

Another solution is to have a global variable (e.g. our $conf = MyConf->new(...) in the main script and then access it via $main::conf from every part of the code. This kind of package global variable can work, but it does not look good. In every place we now have to hard-code the fully qualified name of the variable. Soon we'll find a case (for example a test script) that needs to work differently.

A third solution would be to create a MyConf object in every class. Then we would probably need to pass around the attributes of the constructor. In our example that is the name of the configuration file. Even if we could solve that problem, this solution would mean we need to read the configuration information in every class - and in every object of that class. This is both a run-time and a memory penalty.

A singleton class would allow us to create the instance in the main script using my $conf = MyConf->instance( file => 'conf.ini' ); and then in every class we can just call my $c = MyConf->instance; and we know we get back the exact same object. Without touching the configuration file, and without duplicating the configuration data in the memory.

Loading the content of the configuration file

Getting back to the actual example, it is not enough to have the name of the configuration file passed to the MyConf class. We would also like to load the content of the configuration file.

In this case we assume the configuration file is a simple INI file with sections and key-value pairs:

examples/singleton-moo-config/config.ini

[section A]
name=Foo

[section B]
name=Bar

We can load that using the Config::Tiny module and we can do it in a BUILD subroutine of Moo. This means the configuration file will be loaded the first time we call instance method where we are expected to pass the file => 'config.ini' parameters when we create the object in the main script or setup subroutine.

Later calls to instance won't call the BUILD method any more and thus won't expect to have a file => ... parameter, nor will it even look at it.

examples/singleton-moo-config/MyConf.pm

package MyConf;
use Moo;
with 'MooX::Singleton';
 
use Config::Tiny;
 
has file => (is => 'ro', required => 1);
has conf => (is => 'rw');
 
sub BUILD {
    my ($self) = @_;
 
    print "BUILD called\n";
    my $conf = Config::Tiny->read($self->file, 'utf8')
	    or die sprintf "Could not get configuration from '%s'", $self->file;
    $self->conf($conf);
}
 
1;

In the main script we call:

MyConf->instance( file => 'conf.ini' );

then anywhere in our application we can have the following call to fetch the configuration values:

my $c = MyConf->instance();
my $a_value = $c->conf->{'section A'}{key};

See the main script:

examples/singleton-moo-config/application.pl

use strict;
use warnings;
use 5.010;
 
use MyConf;
use ModuleA;
use ModuleB;

sub setup {
    MyConf->instance(file => 'config.ini');
}

setup();

ModuleA->new->do_something();
ModuleB->new->do_something_else();

and two modules used by that script. In ModuleA we call instance without passing any parameter:

examples/singleton-moo-config/ModuleA.pm

package ModuleA;
use 5.010;
use Moo;

sub do_something {
    my ($self) = @_;

    my $c = MyConf->instance();
    my $a_name = $c->conf->{'section A'}{name};
    say $a_name;
}

1;

In ModuleB we pass a new file => ... parameter, but it is disregarded by the code. The BUILD function is not even called:

examples/singleton-moo-config/ModuleB.pm

package ModuleB;
use 5.010;
use Moo;

sub do_something_else {
    my ($self) = @_;

    my $c = MyConf->instance(file => 'other.ini');
    my $a_name = $c->conf->{'section A'}{name};
    say $a_name;
}

1;

Running that script we get the following output:

$ perl application.pl
BUILD called
Foo
Foo