Singletons and Companions

Scala takes a significant departure from Java in how it deals with static fields and methods. Furthermore, it has first-class support for singleton objects. Let’s explore singletons and companions, and see how static is handled in Scala.

Singleton Object

Singleton is a popular pattern discussed in Design Patterns: Elements of Reusable Object-Oriented Software [GHJV95] by Gamma et al. A singleton is a class that has only one instance. We use singletons to represent objects that act as a central point of contact for certain operations such as database access, object factories, and so on.

It turned out that the singleton pattern is easy to understand but hard to implement in Java—see Joshua Bloch’s Effective Java [Blo08]. Thankfully, this problem is addressed in Scala at the language level. To create a singleton use the keyword object instead of class. Since you can’t instantiate a singleton object, you can’t pass parameters to the constructor.

Here’s an example of a singleton called MarkerFactory along with a class named Marker:

WorkingWithObjects/Singleton.scala
 
import​ scala.collection.mutable._
 
 
class​ Marker(​val​ color: ​String​) {
 
println(s​"Creating ${this}"​)
 
 
override​ ​def​ toString = s​"marker color $color"
 
}
 
 
object​ MarkerFactory {
 
private​ ​val​ markers = Map(
 
"red"​ -> ​new​ Marker(​"red"​),
 
"blue"​ -> ​new​ Marker(​"blue"​),
 
"yellow"​ -> ​new​ Marker(​"yellow"​))
 
 
def​ getMarker(color: ​String​) =
 
markers.getOrElseUpdate(color, ​new​ Marker(color))
 
}
 
 
println(MarkerFactory getMarker ​"blue"​)
 
println(MarkerFactory getMarker ​"blue"​)
 
println(MarkerFactory getMarker ​"red"​)
 
println(MarkerFactory getMarker ​"red"​)
 
println(MarkerFactory getMarker ​"green"​)

Here’s the result of running this code:

 
Creating marker color red
 
Creating marker color blue
 
Creating marker color yellow
 
marker color blue
 
marker color blue
 
marker color red
 
marker color red
 
Creating marker color green
 
marker color green

In the example, the class Marker represents color markers with some initial colors. MarkerFactory is a singleton that helps reuse precreated instances of Marker.

You can access the singleton—the only instance—of MarkerFactory by its name. Once you define a singleton, its name represents the single instance of the singleton object.

There is one problem in the previous code, however. We can directly create an instance of Marker without going through MarkerFactory. Let’s look at how to restrict the creation of instances of a class to its singleton factory next.

Stand-alone and Companion Objects

The MarkerFactory we just saw is an example of a stand-alone object. It’s not automatically connected to any class, even though we have used it to manage instances of Marker.

You can associate a singleton to a class if you like. Such a singleton will share the same name as a class name and is therefore called a companion object. The corresponding class is called a companion class. This romance leads to some very powerful capability as we’ll see.

In the previous example, we wanted to regulate the creation of Marker instances. Classes and their companion objects have no boundaries between them—they can access the private fields and methods of each other. Also, constructors of a class, including the primary constructor, can be marked private. We can combine these two facilities to solve the problem highlighted at the end of the previous section. Here’s a rewrite of the Marker example using a companion object:

WorkingWithObjects/Marker.scala
 
import​ scala.collection.mutable._
 
 
class​ Marker ​private​(​val​ color: ​String​) {
 
println(s​"Creating ${this}"​)
 
 
override​ ​def​ toString = s​"marker color $color"
 
}
 
 
object​ Marker {
 
private​ ​val​ markers = Map(
 
"red"​ -> ​new​ Marker(​"red"​),
 
"blue"​ -> ​new​ Marker(​"blue"​),
 
"yellow"​ -> ​new​ Marker(​"yellow"​))
 
 
def​ getMarker(color: ​String​) =
 
markers.getOrElseUpdate(color, ​new​ Marker(color))
 
}
 
 
println(Marker getMarker ​"blue"​)
 
println(Marker getMarker ​"blue"​)
 
println(Marker getMarker ​"red"​)
 
println(Marker getMarker ​"red"​)
 
println(Marker getMarker ​"green"​)

Let’s look at the output of running the previous code:

 
Creating marker color red
 
Creating marker color blue
 
Creating marker color yellow
 
marker color blue
 
marker color blue
 
marker color red
 
marker color red
 
Creating marker color green
 
marker color green

The constructor of Marker is declared private; however, the companion object can access it. Thus, we’re able to create instances of Marker from within the companion object. If you try to create an instance of Marker outside the class or the companion object, you’ll get an error.

Each class may have an optional companion object that can be placed in the same file as their companion classes. Companion objects are common in Scala and provide class-level convenience methods. They also serve as a nice workaround for the lack of static members in Scala, as we’ll see next.

static in Scala

Scala does not have the static keyword; allowing static fields and static methods directly in a class would break the pure object-oriented model that Scala supports. At the same time, Scala fully supports class-level operations and properties through singletons and companion objects.

Let’s revisit the previous Marker example. It would be nice to get the supported colors from Marker. However, it would not make sense to direct such a query on any specific instance of the class; it’s a class-level operation. In other words, if we were coding in Java, we would’ve written that query method as a static method in the Marker class. But, Scala does not provide static. The language has been designed so that such methods reside as regular methods in singletons and companion objects. Let’s modify the Marker example to create methods in the companion object:

WorkingWithObjects/Static.scala
 
import​ scala.collection.mutable._
 
 
class​ Marker ​private​ (​val​ color: ​String​) {
 
override​ ​def​ toString = s​"marker color $color"
 
}
 
object​ Marker {
 
private​ ​val​ markers = Map(
 
"red"​ -> ​new​ Marker(​"red"​),
 
"blue"​ -> ​new​ Marker(​"blue"​),
 
"yellow"​ -> ​new​ Marker(​"yellow"​))
 
 
def​ supportedColors = markers.keys
 
def​ apply(color: ​String​) = markers.getOrElseUpdate(color, ​new​ Marker(color))
 
}
 
println(s​"Supported colors are : ${Marker.supportedColors}"​)
 
println(Marker(​"blue"​))
 
println(Marker(​"red"​))

This is the output from running the code:

 
Supported colors are : Set(yellow, red, blue)
 
marker color blue
 
marker color red

We wrote the method supportedColors in the companion object—the parentheses in the method definition are optional if the method takes no parameters. We call it on the Marker companion object like we’d call static methods on classes in Java.

The companion object also provides another benefit: the ability to create instances of the companion class without the need for the new keyword. The special apply method does this trick. In the previous example, when we invoke Marker("blue"), we’re actually calling Marker.apply("blue"). This is a lightweight syntax to create or get instances.

Let’s quickly look under the hood to see how the methods in singletons or companion objects get compiled into bytecode. Here’s a singleton with a method:

WorkingWithObjects/Greeter.scala
 
object​ Greeter {
 
def​ greet() = println(​"Ahoy, me hearties!"​)
 
}

To see the code generated by the Scala compiler, run the following commands:

 
scalac Greeter.scala
 
javap -private Greeter

The output from the call to javap is

 
Compiled from "Greeter.scala"
 
public final class Greeter {
 
public static void greet();
 
}

The method in the singleton has been created as a static method at the bytecode level. This is good news from the Java interoperability point of view.

If you take a peek at the files created from the previous compilation, you’d notice, not one, but two class files created from Greeter.scala. We will discuss the details of the additional class and some of the challenges with interoperability that Scala may pose in Chapter 14, Intermixing with Java.

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

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