官术网_书友最值得收藏!

Scala singletons

Scala has singleton objects called companion objects. A companion object is an object with the same name as a class. A companion object also can access private methods and fields of its companion class. Both a class and its companion object must be defined in the same source file. The companion object is where the apply() factory method may be defined. Let's have a look at the following example of a companion class:

class Singleton {  // Companion class
  def m() {
    println("class")
  }         
}

And then its companion object as:

object Singleton { // Companion Object
  def m() {
    println("companion")
  }         
}

It is that simple, when a case class is defined, Scala automatically generates a companion object for it.

The apply() factory method

If a companion object defines an apply() method, the Scala compiler calls it when it sees the class name followed by (). So, for example, when Scala sees something like:

                 Singleton(arg1, arg2, …, argN) // syntactic sugar

It translates the call into:

                 Singleton.apply(arg1, arg2,...,argN) 

Open a Scala console and enter the following:

scala> val p = Map("one" -> 1, "two" -> 2)
p: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2)

When we say Map("one" -> 1, "two" -> 2), it seems like we are calling a function named Map with the arguments—however, this form is just syntactic sugar. Under the hood, Map has a companion object whose apply method is being called, then the apply() method creates the Map object and assigns it to p.

Every object can be called as a function, provided it has the apply method. For example, we can add an apply method ourselves and get the benefit of the syntactic sugar. The following uses REPL's paste mode. The paste mode, enabled via the paste command, allows you to enter multiline code snippets for evaluation:

scala> :paste
// Entering paste mode (ctrl-D to finish)
class C(x: Int) {
 def print() = println(x)
}
object C {
 def apply(n: Int) = {
 new C(n)
 }
}

press Ctrl + D here:

// Exiting paste mode, now interpreting.
defined class C
defined object C
scala> val k = C(9)
k: C = C@4b419a6b
scala> k.print()
9

The expression C(9) looks very much like a function call. As we see it is expanded to C.apply(...).

Wait a minute! Does this apply to functions, too? Yes:

scala> val f = (l: List[Int]) => l.map(_ * 2)

f: List[Int] => List[Int] = <function1>

scala> f(List(1,2,3))

res0: List[Int] = List(2, 4, 6)

When we call f(arg) it gets translated to f.apply(arg).

The factory method pattern

A factory method can further hide the actual concrete class implementing an interface. You have the factory method pattern, shown as follows:

Figure 2.4: Factory Method UML

Also let's try the following program:

public interface Currency {
 public String getConversionRateToIndianRupee();
}
public class CurrencyConverter {
 private static final String YEN = "Yen";
 private static final String EURO = "Euro";
 private static final String DOLLAR = "Dollar";

 private static final class EuroToRupee implements Currency {
  @Override
  public String getConversionRateToIndianRupee() {
   return "82";
  }
 }

 private static final class DollarToRupee implements Currency {
  @Override
  public String getConversionRateToIndianRupee() {
   return "60";
  }
 }
 private static Currency createCurrencyFor(final String currencyStr) {
  if (currencyStr.equals(DOLLAR)) {
   return new DollarToRupee(); 
  }
  if (currencyStr.equals(EURO)) {
   return new EuroToRupee();
  }
  throw new IllegalArgumentException("Oops! no idea about <"
    + currencyStr + ">");
 }

 public static void main(String[] args) {
  System.out.println(createCurrencyFor(DOLLAR)
    .getConversionRateToIndianRupee());
  System.out.println(createCurrencyFor(EURO)
   .getConversionRateToIndianRupee());
  System.out.println(createCurrencyFor(YEN)
    .getConversionRateToIndianRupee());
 }
}

All the knowledge of the conversion rule is in one place and hidden by design from the outside world. This hiding helps us edit existing rates and extend for more currencies all in one place. The big advantage is we get a single point where we can change, knowing the change will correctly reflect everywhere.

The Scala version

The Scala version is simpler, we just use the apply method:

trait Currency {
 def getConversionRateToIndianRupee: String
}

object CurrencyConverter {
 private object EuroToRupee extends Currency {
 override def getConversionRateToIndianRupee = "82"
 }

 private object DollarToRupee extends Currency {
 override def getConversionRateToIndianRupee = "60"
 }

 private object NoIdea extends Currency {
 override def getConversionRateToIndianRupee = "No Idea"
 }

 // the currency factory method
 // Note: Scala if statement is an expression. 
 def apply(s: String):Currency = {
 if (s == "Dollar") // same as s.equals("Dollar") in Java 
DollarToRupee
 else if (s == "Euro")
 EuroToRupee
 else
 NoIdea
 }
}

val c = CurrencyConverter("Dollar") // apply method in action 
c.getConversionRateToIndianRupee // outputs "60"

Point to ponder: How could we rewrite the apply(...) method?

The apply method has one if expression, its value is the return value.

Note

Difference from Java. In Scala, the if/else statement has a value.

This allows me to write:

scala> def m(x: Int) = if (x < 0) true else false
m: (x: Int)Boolean

scala> m(10)
res35: Boolean = false

scala> m(-10)
res36: Boolean = true
主站蜘蛛池模板: 泗阳县| 西丰县| 彩票| 本溪| 肥乡县| 政和县| 安平县| 万山特区| 夏河县| 铜鼓县| 湟中县| 兰溪市| 大渡口区| 上犹县| 吴川市| 日喀则市| 昌乐县| 萨迦县| 拜城县| 嘉义市| 隆化县| 车致| 都匀市| 沙田区| 拉萨市| 罗平县| 丹凤县| 堆龙德庆县| 兴国县| 雷州市| 建阳市| 肥西县| 肥乡县| 隆林| 肇东市| 镇赉县| 大渡口区| 普定县| 萨嘎县| 鄂伦春自治旗| 琼结县|