This post was posted by Captain on the ScalaCool team blog.

In the last Java to Scala series, I think you have more or less a grasp of object in terms of language features. After understanding its cool language features — let static return to normal and simple use of its derived methods, I would like to talk about my understanding of object in practical application. I don’t know if it will also give you a new and refreshing feeling, after all, “singleton object” as a natural language feature, flashy is not what we want to see.

Singleton pattern VS singleton object

We already know that object exists as a “singleton” that breaks the static state. In Scala, the frequency of singleton use can be compared to the new keyword in Java or DI (Dependency Injection) in Spring. So we had to consider some scenarios — multithreading and performance overhead. Now let’s look at how it differs from the singleton pattern implemented in Java.

Let’s take a look at Java’s implementation of the singleton pattern:

The hungry mode

public class UniqueSingleton {
  // Class is initialized when it is loaded
  private static uniqueSingleton instance = new uniqueSingleton();
  private UniqueSingleton(a) {
    System.out.println(("UniqueSingleton is created"));
  }
  public static UniqueSingleton getInstance(a) {
    returninstance; }}Copy the code

The singleton pattern is implemented with these few lines of code, and it’s as simple as that. The disadvantage of the hungry mode, however, is that it loads the singleton while the JVM loads the class, whether you call it or not, so it doesn’t have lazy passing (or lazy loading in Java).

Lazy mode

public class UniqueSingleton {
  // Class load is not initialized
  private static uniqueSingleton instance;
  private UniqueSingleton(a) {
    System.out.println(("UniqueSingleton is created"));
  }
  public static UniqueSingleton getInstance(a) {
    if (instance == null) {
   	  instance = new UniqueSingleton();
    }
    returninstance; }}Copy the code

To solve this problem, we naturally have to consider lazy loading, and the solution is very simple, as you can see, to add a judgment condition before creating. But is it really all right? It’s not. In a single-threaded environment, this is a perfect solution, but in a multi-threaded environment? If multiple instances are being created at the same time, the common solution is to add the synchronize keyword to the start of the entire getInstance method, but this incurs a significant performance overhead, which is not desirable. Here I have to mention the question and answer of a great god on the Internet. He proposed a solution — use an enum.

Enumeration class implementation

public enum EnumExample {
  INSTANCE;
  private final String[] favoriteComic =
    { "fate"."Dragon Ball" };
  public void printFavorites(a) { System.out.println(Arrays.toString(favoriteComic)); }}Copy the code

In addition to its simplicity, enum also provides a serialization mechanism that prevents the creation of new objects, and this answer received the highest number of votes on Stack Overflow.

The object to realize

The object keyword creates a new singleton type, just as a class has only one named instance. If you’re familiar with Java, declaring an Object in Scala is a bit like creating an instance of an anonymous class. — From Scala functional programming

In fact, I have listed so many Java implementations of the singleton pattern and the continuous optimization of different scenarios in order to introduce Object, because object does not have to consider so much, as Java is not constrained by the scene.

Here’s an example:

object Singleton {
  def sum(l: List[Int) :Int = l.sum
}
Copy the code

Doesn’t the code look instantly more elegant? If you’re interested, you can decompile with $javap and see that all methods are preceded by the static keyword, which I won’t go into here.

This is because thread safety no longer has to worry about whether it is single-threaded or multi-threaded, or because lazy loading can be initialized on demand with the lazy keyword.

Optimize the traditional factory model

As you know, one of the most common things we do in code is to create an object first, just as we build a house before we build a foundation. We want to have a pattern to make it easier for us to use it, convenient for later maintenance, the creation pattern has emerged, and the factory pattern is one of the main characters in the creation pattern, I think this design pattern for everyone familiar with Java should be a piece of cake. In fact, factory patterns are divided into three categories — simple factory patterns, factory patterns, and abstract factory patterns. I prefer to divide it into two categories. In my opinion, the simple factory pattern is more like a special case of the factory pattern, not strictly a pattern, but it does decouple the logic of creating instances from the client.

Here, I’ll implement the simple factory pattern in two different languages, Scala and Java, to give you a better idea of object. Suppose we have a computer equipment manufacturer that produces both mice and keyboards, and we describe its business logic using the familiar simple factory pattern design. Let’s start with Java:

// Define the product interface
public interface Product{
  public void show(a);
} 
// The specific product classes are implemented below
public class Mouse implements Product {
  @Override
  public void show(a) {
    System.out.println("A mouse has been built"); }}public class Keyboard implements Product {
  @Override
  public void show(a){
    System.out.println("A keyboard has been built"); }}public class SimpleFactory {
  public Product produce(String name) {
    switch (name) {
      case "Mouse":
        return new Mouse();
      case "Keyboard":
        return new Keyboard();
      default:
        throw newIllegalArgumentException(); }}}// Easy to use
public class Test {
  public static void main(String[] args) {
    SimpleFactory simpleFactory = new SimpleFactory();
    Mouse mouse = simpleFactory.produce("Mouse"); mouse.show(); }}Copy the code

The above code creates different Product subclasses by calling the produce method in SimpleFactory, so as to decouple the instance creation logic from the client. Here, I implement the SimpleFactory mode by directly judging the incoming key. There are other ways — newInstance reflection and so on. So how, one might ask, does that work in Scala? This is where our hero, the singleton, comes in.

Use singletons instead of factory classes

Remember that Scala supports object to implement the singleton pattern in Java, so instead of a factory class we can implement a SimpleFactory singleton as follows:

trait Product {
  def show()}case class Mouse() extends Product {
  def show = println("A mouse has been built")}case class Keyboard() extends Product {
  def show = println("A keyboard has been built")}object SimpleFactory  {/ / object instead of the class
  def produce(name: String) :Product =  name match {
    case "Mouse"= >Mouse(a)case "Keyboard"= >Keyboard()}}object Test extends App {
  val mouse: Mouse = SimpleFactory.produce("Mouse")
  mouse.show()
}
Copy the code

As you can see from the above code, Scala can easily implement the same method of judging values. But that’s not the most important thing. It’s worth noting that we no longer need to create the SimpleFactory object before testing it. This is exactly the convenience of the object static property at the application level. Don’t worry, Scala also provides us with a syntactic sugar called apply, which is essentially like a constructor, as discussed in the previous article. In fact, it can also be applied to the factory pattern. In this way, we can omit the factory class and simply add a companion object to the product-class interface.

The accompanying object creates a static factory method

We create different objects by judging the name string passed in, so the produce() method is a bit redundant. How to make the implementation of factory mode more perfect? Creating with a companion object solves this problem:

object Product {
  def apply(name: String) :Product = name match {
   	case "Mouse"= >Mouse(a)case "Keyboard"= >Keyboard()}}Copy the code

Then, we can call it like this

val mouse: Product = Product("Mouse")
val keyboard: Product = Product("Keyboard")
mouse.show()
keyboard.show()
Copy the code

Does that make the call experience better? As you can see, we have improved the implementation of design patterns in Java to some extent by taking advantage of object features, and the simple factory pattern is just the tip of the iceberg.

Because space is limited to listing only simple factory patterns again, as for method factory patterns and abstract factory patterns, you can look at the source code if you are interested.

Finally, in a word, Object, as a natural language feature that breaks the static state and returns to normal, has optimized some features of Java so that we can better understand and use it. Do you have any resonance with me?