The Singleton Design Pattern in Scala
Photo by toddquackenbush on Unsplash

The Singleton Design Pattern in Scala

In this article I will briefly explain the singleton pattern, using some code examples. This will be done from the perspective of Scala, a pure object and functional oriented language. Code snippets can be found on my?github.

Design Patterns

In software there are a lot of industries such as mobile phone, website, big data, etc. These industries tackle a huge load of programming problems. Nevertheless in programming we usually come up with problems that are the same that other programmers have tackled or somehow similar, and there is a reusable solution that is considered the most correct way and is like a blueprint of how the problem needs to be solved, that is a design pattern. These design patterns are needed sometimes due to the lack of expressiveness of the language, that it doesn’t make available a simple solution. The expressiveness and the functionalities available in a programming language also can make available some additional design patterns that other programming languages don′t, due to the lack of certain functionality. The purpose of these design patterns is to obtain code that is more efficient, extensible, testable and readable.

Design patterns in Scala

Scala it’s a rich expressive language and it makes some of the design patterns not necessary or simpler. Also, due to its functional oriented capabilities, it’s gonna make additional design patterns available that other traditional object oriented programming languages don’t have.

Creational Patterns

The singleton pattern it’s classified as a creational design pattern. Creational design patterns deal with the complexity of object creation providing a better alternative from direct object creation. The complexity of object creation comes in the form of a large number of parameters, difficulties obtaining these parameters and difficulties in the validation of the objects created with these parameters.

Singleton Pattern

The singleton pattern consists of a type which has a unique object constructed for it. This introduces a global state in the Scala application and it ensures that all clients which access the type will access the same object from a well known access point. The singleton object due to this is usually used for utility classes. An example of a singleton object would be a class that manages the connections to an EC2 instance. In Scala the singleton pattern is supported out of the box for its simplest form, through the object keyword. An object in Scala is like a static class, it has all methods and attributes static, that’s why in Scala the static keyword doesn’t exist, because it’s not needed as an object can be used for the purpose of having static methods, attributes and classes. The constituent parts of the singleton pattern are the singleton itself and the clients that consume this object. This is shown in the following diagram.

No hay texto alternativo para esta imagen

In this example the client uses the EC2Con object for connecting to an EC2 instance which contains an id attribute of type int and a connect method where this id is used and the connection to a EC2 instance established. This singleton is lazily initialized, and when creating it, dynamic parameters cannot be provided. The code for this implementation is the following.

object EC2Con 
  val id: Int = 1
  def connect: String = s"singleton EC2 connection with session id ${id} stablished"
}

object SimpleClient extends App {
  System.out.println(EC2Con.connect)
}{        

This is a simple implementation that comes out of the box by Scala. Due to this, it is argued that Scala takes away all considerations of how to implement the singleton pattern. Nevertheless this is true for its simplest form, whose simplicity is misleading, as complex considerations can arise that need to be implemented. The initialization perhaps instead of being lazy should be eager. The object could need to be destroyed after using it, or after a period of time of not being used due to the amount of resources needed for it. The singleton instance could have a lifecycle and it is needed to perform some operations after and before instantiation and destruction. The singleton perhaps should be extendable and this extension shouldn’t affect clients code. Instead of one singleton instance could be needed an specific number of singleton instances(duoton, tripleton, etc).

For providing a solution for these considerations this simple implementation needs to be expanded taking advantage of the factory pattern and delegate pattern. The following diagram shows this solution with the conjunction of both patterns.

No hay texto alternativo para esta imagen

Through the delegation pattern a delegator class reuses behavior by delegating behavior to another type. In the following link you can find an extend explanation about this pattern?The Delegation Design Pattern in Scala. In this case EC2MemOptDelegator class which extends EC2 abstract class delegates the implementation of its behavior, the id attribute and the connect method, to EC2MemOpt class. The EC2MemOpt is a singleton class and its behavior it’s accessed from the delegator by the getEC2MemOptIns method from the ECConnectionFactory. The EC2MemOpt class contains a create and a destroy method that is used to control the lifecycle of the class to perform some operations after the creation and before the destruction of the singleton instance of the class. The same logic follows the delegator class EC2ComOptDelegator and its delegate class EC2ComOpt. The delegate instances are private to the package as the client shouldn’t have access to them, only the delegator classes from the same package. As well the methods getEC2MemOptIns and getEC2ComOptIns are private to the package as only the delegator classes should have access to these methods for obtaining its corresponding delegates.

The delegation pattern is necessary in order to obtain different delegator instances whose behavior is implemented in the singleton delegate instance in order to provide clients with different instances that access the behavior of a unique object. The following it’s the implementation of these classes.

abstract class EC2Con 
  val id: Int
  def connect: String
}

private[complex] class EC2MemOpt extends EC2Con {
  override val id = 1
  override def connect: String = s"singleton EC2MemOpt connection with session id ${id} established"
  def create: Unit = System.out.println(s"singleton EC2MemOpt connection with session id ${id} created")
  def destroy: Unit = System.out.println(s"singleton EC2MemOpt connection with session id ${id} destroyed")
}

private[complex] class EC2ComOpt extends EC2Con {
  override val id = 2
  override def connect: String = s"singleton EC2ComOpt connection with session id ${id} established"
  def create: Unit = System.out.println(s"singleton EC2ComOpt connection with session id ${id} created")
  def destroy: Unit = System.out.println(s"singleton EC2ComOpt connection with session id ${id} destroyed")
}

class EC2MemOptDelegator extends EC2Con {
  override val id = EC2ConnectionFactory.getEC2MemOptIns.id
  override def connect: String = EC2ConnectionFactory.getEC2MemOptIns.connect
}

class EC2ComOptDelegator extends EC2Con {
  override val id = EC2ConnectionFactory.getEC2ComOptIns.id
  override def connect: String = EC2ConnectionFactory.getEC2ComOptIns.connect
}{        

Through the factory pattern the client uses a factory object that instantiates different objects that inherit from a given trait or superclass. In the following link you can find an extend explanation about this pattern?The Factory Design Patterns in Scala. In this case the factory object it’s EC2ConnectionFactory, that instantiates different objects that inherit from a given trait or superclass. The classes that will be instantiated are EC2MemOptDelegator and EC2ComOptDelegator, which inherit from EC2 abstract class. The instances of these classes will provide access to the behavior of the singleton instances of EC2MemOpt and EC2ComOpt stored in the private attributes ec2MemOptIns and ec2ComOptIns of the factory. These singleton instances are lazily instantiated because its type is an option value whose default value is None. They could be provided eagerly by having a default value of an instance of the EC2MemOpt and EC2ComOpt classes. The delegate instances are created when obtaining one of the delegator instances through EC2MemOpt and EC2ComOpt methods from the factory, after accessing the id attribute or connection method from these classes, which both are implemented by the delegate singleton instances. The factory EC2ConnectionFactory hides this complexity of object creation from the client by providing these two methods to access both delegators respectively. The following is the implementation of the factory.

object EC2ConnectionFactory 
  private[complex] var ec2MemOptIns: Option[EC2MemOpt] = None
  private[complex] var ec2ComOptIns: Option[EC2ComOpt] = None

  def EC2MemOpt: EC2Con = {
    new EC2MemOptDelegator()
  }

  private[complex] def getEC2MemOptIns: EC2Con = {
    if (ec2MemOptIns.isEmpty) {
      this.ec2MemOptIns = Option(new EC2MemOpt)
      this.ec2MemOptIns.get.create
    }
    this.ec2MemOptIns.get
  }

  def destroyEC2MemOptIns= {
    if (this.ec2MemOptIns.nonEmpty) {
      this.ec2MemOptIns.get.destroy
      this.ec2MemOptIns = None
    }
  }

  def EC2ComOpt: EC2Con = {
    new EC2ComOptDelegator()
  }

  private[complex] def getEC2ComOptIns: EC2Con = {
    if (this.ec2ComOptIns.isEmpty) {
      this.ec2ComOptIns = Option(new EC2ComOpt)
      this.ec2ComOptIns.get.create
    }
    this.ec2ComOptIns.get
  }

  def destroyEC2ComOptIns = {
    if (this.ec2ComOptIns.nonEmpty) {
      this.ec2ComOptIns.get.destroy
      this.ec2ComOptIns = None
    }
  }
}{        

When creating the delegate instances, it is called the getEC2MemOptIns or getEC2ComOptIns from the delegators. When calling these methods, the create method of the corresponding delegate is also called after obtaining the singleton instance. The client can destroy the singleton instances by calling the destroyEC2MemOptIns or destroyEC2ComOptIns from the factory, depending on the delegate intended to be destroyed. Additional logic is executed before destruction of the delegate using the destroy method of the corresponding delegate intended to be destroyed.

The factory method is necessary for providing instances to the client that extends the EC2Con abstract class in order to have more concise and easy to use code. This it’s achieved because in the client code there is no need to execute concrete methods of each EC2Con delegator implementation as all EC2Con delegator implementations share the same methods signatures and the concrete EC2Con delegator implementations can be used together.

Through the factory object the lifecycle of the instances is managed through the logic to execute after creation and before destruction using the create and destroy methods of the delegates, and the complexity of this is hidden from the client.

Through the factory method pattern better extendibility is also managed. The client code it’s decoupled from the code that creates the objects.This makes the object creation code more extensible as it can be extended without needing to change the code that uses it. For obtaining a new extension of EC2Con abstract class it is only needed an implementation of a delegator and a delegate extending both EC2Con, and a factory method for obtaining the delegator whose behavior is implemented by the delegate. A destroy method to the new delegate can be added to the factory the same way as with the previous delegates. This can be performed without affecting client code improving extendibility. An example of an implementation of a client using the factory is the following.

object ComplexClient extends App 
  System.out.println("Creating first connector")
  val ec2MemOptIns1: EC2Con = EC2ConnectionFactory.EC2MemOpt
  System.out.println("Getting the first connector again using reference, no instantiation")
  val ec2MemOptIns2: EC2Con = EC2ConnectionFactory.EC2MemOpt
  System.out.println("Establishing connection with references to the connector")
  System.out.println(ec2MemOptIns1.connect)
  System.out.println(ec2MemOptIns2.connect)
  System.out.println("Destroying connector")
  EC2ConnectionFactory.destroyEC2MemOptIns
  System.out.println("Trying to establish connection with destroyed connector")
  System.out.println(ec2MemOptIns1.connect)
}{        

Singleton Design Pattern as an Anti-Pattern

An anti-pattern is defined in the book?AntiPatterns?by Brown et al, 1998 as “A literary form that describes a commonly occurring solution to a problem that generates decidedly negative consequences”. Design patterns depending on the context come with some advantages, but if the disadvantages are considered to outweigh the advantages, then the pattern is considered an anti-pattern. As it has been explained the singleton pattern ensures that only one instance of a class exists, which can provide performance advantages. Nevertheless the singleton object sometimes is used with a mutable state as a replacement for techniques like partial function application or dependency injection, and it ends up becoming a global variable. It is agreed that this should be avoided and replaced by other techniques.

Liked the content?

You can subscribe to receive an email every time I publish a new story

Want to connect?

Linkedin,?Medium,?GitHub

要查看或添加评论,请登录

Santos Saenz Ferrero的更多文章

社区洞察

其他会员也浏览了