The singleton design pattern

The singleton design pattern ensures that a class has only one object instance in the entire application. It introduces a global state in the applications it is used in.

A singleton object can be initialized using different strategies—lazy initialization or eager initialization. This all depends on the intended use, the time it takes an object to be initialized, and so on.

Class diagram

Singletons are another example of design patterns, which are supported out of the box by the Scala programming language syntax. We achieve this using the object keyword. In this case, again, providing a class diagram is not necessary, so we will step right into the example in the next subsection.

Code example

The aim of this example is to show how to create singleton instances in Scala and have an understanding of when exactly instances are created in Scala. We will look at a class called StringUtils, which provides different utility methods related to strings:

object StringUtils {

  def countNumberOfSpaces(text: String): Int = text.split("\s+").length - 1
}

Using this class is then straightforward. Scala takes care of creating the object, thread safety, and so on:

object UtilsExample {
  def main(args: Array[String]): Unit = {
    val sentence = "Hello there! I am a utils example."
    System.out.println(
      s"The number of spaces in '$sentence' is: ${StringUtils.countNumberOfSpaces(sentence)}"
    )
  }
}

The output from this program will be the following:

Code example

The preceding example is clear and even though the StringUtils object will be a singleton instance, it more resembles a class with static methods. This is actually how static methods are defined in Scala. It would be more interesting to add some state to a singleton class. The following example shows exactly this:

object AppRegistry {
  System.out.println("Registry initialization block called.")
  private val users: Map[String, String] = TrieMap.empty
  
  def addUser(id: String, name: String): Unit = {
    users.put(id, name)
  }
  
  def removeUser(id: String): Unit = {
    users.remove(id)
  }
  
  def isUserRegistered(id: String): Boolean = users.contains(id)
  
  def getAllUserNames(): List[String] = users.map(_._2).toList
}

The AppRegistry contains a concurrent map of all the users currently using the application. This is our global state and we have methods that allow us to manipulate it. We also have a println statement, which will be executed when the singleton instance is created. We can use our registry in the following application:

object AppRegistryExample {
  def main(args: Array[String]): Unit = {
    System.out.println("Sleeping for 5 seconds.")
    Thread.sleep(5000)
    System.out.println("I woke up.")
    AppRegistry.addUser("1", "Ivan")
    AppRegistry.addUser("2", "John")
    AppRegistry.addUser("3", "Martin")
    System.out.println(s"Is user with ID=1 registered? ${AppRegistry.isUserRegistered("1")}")
    System.out.println("Removing ID=2")
    AppRegistry.removeUser("2")
    System.out.println(s"Is user with ID=2 registered? ${AppRegistry.isUserRegistered("2")}")
    System.out.println(s"All users registered are: ${AppRegistry.getAllUserNames().mkString(",")}")
  }
}

Let's run this example and see what the final output will be:

Code example

Now our example presents a proper singleton instance, which contains a global state. This state will be accessible from all the application classes while the instance runs. From the example code and our output, we can make a few conclusions:

Note

Singletons in Scala are lazily initialized.

While creating a singleton instance, we cannot provide dynamic parameters to the singleton class instance.

What is it good for?

In Scala, the singleton design pattern and static methods are implemented the same way. That's why singletons are useful for creating utility classes that have no state. Singletons in Scala can be also used to build ADTs, which we talked about in the previous chapters.

Another thing that is strictly valid for Scala is that in Scala, singletons are created in a thread-safe way out of the box and without the need to take any special care.

What is it not so good for?

Often the singleton design pattern is actually considered an anti-pattern. Many people say that global state should not exist the way it does with singleton classes. Some say that if you have to use singletons, you should try and refactor your code. While this is true in some cases, there is sometimes good use for singletons. Generally, the rule of thumb is: if you can avoid them, then do.

Another thing that could be pointed out specifically for Scala singletons is that they can really have only one instance. While this is the actual definition of the pattern, with other languages, we could have a predefined number of more than just one singleton object and have some control on this using custom logic.

This does not really affect Scala but it is still worth mentioning. In the case where a singleton is initialized lazily in an application, in order to provide thread safety, one needs to rely on locking mechanisms, for example, the double-checked locking mentioned in the previous section. Accessing the singletons in an application, no matter if it's Scala or not, also needs to be done in a thread-safe way or the singleton should take care of this internally.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.70.185