top of page
Search

Singleton Objects And Companions

Written by Nihal Kashyap


Scala, being both an object-oriented and functional programming language, provides several powerful object-oriented features. In this article, we will discuss some of these features, including singleton objects and companion objects and classes.


Scope

  • The article explains singleton objects, companion class and companion object.

  • It explains the implementation of the Singleton design pattern using singleton objects, which is a popular object-oriented design pattern.

  • The article further explains how companion object can be used along with factory methods to create objects in a concise and expressive way.


Singleton objects


A Singleton object in Scala is a class that has only one instance. Since there can exist only a single instance of the class, they are known as singleton objects. They are globally accessible throughout the program. They are evaluated lazily i.e., when they are accessed or referenced for the first time. And subsequent references to the object will use the already existing instance.


What problem does it solve?


Many times we need only a single instance of a class throughout the codebase, such as a database connection or cache connection. The process of restricting object creation to a single instance is known as the singleton design pattern. In Scala, we can achieve it by using singleton objects.


Usage and application


Here is an example of a Singleton object that defines a cache connection.



object CacheConnection {
	val host: String = "127.0.0.0"
	val port: String = "11211"
}

A singleton object is created using the keyword object. It does not have a constructor. This is obvious because a constructor would return a new object every time it is called. The absence of a constructor ensures that only a single object is created.


Member variables and methods of a singleton object can be accessed by using the object name followed by the dot operator (`.`) and the member variable or method name. For example, we can access the host member variable as `CacheConnection.host`.


Companions


An object and a class sharing the same name and defined in the same source file are called companions. Companions can access each other's private fields and methods.


What problem does it solve?


Let's try to model the Citizen class of a country. The attributes of the Citizen class are:

  • name

  • dob

  • address

  • nationality


The `nationality` field is independent of an object or remains the same for all instances of the class. In many programming languages, most notably in Java, fields and methods of a class that are independent of the instances are known as static members.


Now, the problem is that all objects of the `Citizen` class for a particular country will have the same value for `nationality`. This repetition of the member variables also increases the memory usage.


To avoid this, we can segregate the static members and the non-static members into companion object and companion class, respectively.


Usage and application


class Citizen (val name: String, val dob: String, val address: String)


object Citizen {
	val nationality: String = "Indian"
}

Now, only a single instance will contain the `nationality` variable.


The non-static variables i.e., `name`, `dob` and `address` reside in the class and the static variable i.e., `nationality` reside in the companion object.



val johnDoe: Citizen = new Citizen("John Doe", "12-06-1990", "Mumbai, Bandra")


println(johnDoe.name) // John Doe
println(Citizen.nationality) // Indian

The All-powerful apply() method


If objects contain the `apply()` method then, there is no need to explicitly invoke it. We can directly pass the parameters of the `apply()` method to the object.


Let's put an `apply()` method inside the `Citizen` class and try to access it using only an object of `Citizen`.



class Citizen (val name: String, val dob: String, val address: String) {
def apply(greetingMsg: String) = println(greetingMsg)
}


val johnDoe: Citizen = new Citizen("John Doe", "12-06-1990", "Mumbai, Bandra")


johnDoe("Hi, everyone!") // Hi, everyone!

Although, here we have invoked the `apply()` method by simply passing its parameters to the object, apart from that, the `apply()` method does nothing. The best application of the `apply()` method is to use it as a factory method.


Factory methods are methods that create objects of a class. They encapsulate the process of object creation and abstracts away the logic of object creation. In the following code example we will see how this is achieved.


We will define an `apply()` method in the `Citizen` companion object that will take the constructor parameters of the companion class as its parameters. The purpose of this `apply()` method is to create objects of the `Citizen` class.


class Citizen (val name: String, val dob: String, val address: String)


object Citizen {
	val nationality: String = "Indian"
def apply(name: String, dob: String, address: String) = new Citizen(name, dob, address)
}

val johnDoe: Citizen = Citizen("John Doe", "12-06-1990", "Mumbai, Bandra")

We can see that in the above code snippet, the creation of objects just got easier by passing the constructor parameters to the companion object without using the `new` keyword. Thus, the use of the `apply()` method as a factory in a companion object allows us to create objects in a concise and expressive way.



Summary


  • Singleton objects allow us to create only a single instance of a class.

  • They are evaluated lazily, i.e., when accessed/ referenced for the first time.

  • Singleton objects allow us to implement the Singleton design pattern in a cleaner and fewer lines of code.

  • Companions are a pair of class and object sharing the same name and defined in the same file.

  • They can access each other's private member variables and methods.

  • An important application of companions is segregating static and non-static members into the Companion object and the Companion classes respectively, there by avoiding the creation of duplicate member variables across objects and reducing memory usage.


When the Companion object is used with the `apply()`method as a factory, it simplifies the creation of objects for its companion class.



Comentários


bottom of page