程式扎記: [Scala IA] The Basics : Ch3. OOP in Scala - Packaging & imports

標籤

2016年8月3日 星期三

[Scala IA] The Basics : Ch3. OOP in Scala - Packaging & imports

Packaging (p61) 
package is a special object that defines a set of member classes and objects. The Scala package lets you segregate code into logical groupings or namespaces so that they don’t conflict with each other. In Java you’re only allowed to have package at the top of the .java file, and the declaration defines the scope across the file. Scala takes a different approach for packaging. It combines Java’s declaration approach with C#’s scoped approach. You can still use the traditional Java approach and define package at the top of the Scala file, or use a scoping approach, as demonstrated in the following listing. 
- Listing 3.1 Declaring packages using the scoping approach 
  1. package com {  
  2.     package scalainaction {  
  3.         package mongo {  
  4.             import com.mongodb.Mongo  
  5.             class MongoClient(val host:String, val port:Int) {  
  6.                 require(host != null"You have to provide a host name")  
  7.                 private val underlying = new Mongo(host, port)  
  8.                 def this() = this("127.0.0.1"27017)  
  9.             }  
  10.         }  
  11.     }  
  12. }  
Here you’re creating the com.scalainaction.mongo package for the MongoClient class. The previous code is exactly equivalent to the following code, where you’re declaring the package in traditional Java style: 
  1. package com.scalainaction.mongo  
  2. import com.mongodb.Mongo  
  3. class MongoClient(val host:String, val port:Int) {  
  4.     require(host != null"You have to provide a host name")  
  5.     private val underlying = new Mongo(host, port)  
  6.     def this() = this("127.0.0.1"27017)  
  7. }  
You can also use curly braces with top-level package declarations like the following: 
  1. package com.scalainaction.mongo {  
  2.     import com.mongodb.Mongo  
  3.     class MongoClient(val host:String, val port:Int) {  
  4.         require(host != null"You have to provide a host name")  
  5.         private val underlying = new Mongo(host, port)  
  6.         def this() = this("127.0.0.1"27017)  
  7.     }  
  8. }  
It’s a matter of style; you can use either one of them. The scoping approach shown in listing 3.1 provides more flexibility and a concise way to lay out your code in different packages. But it might quickly become confusing if you start to define multiple packages in the same file. The most widely used way in Scala code bases is the traditional way of declaring a package at the top of the Scala file. The only large, open source project I know of that uses the package-scoping approach is the Lift web framework (http://liftweb.net). 

One more interesting point to note here is that Scala package declaration doesn’t have to match the folder structure of your filesystem. You’re free to declare multiple packages in the same file: 
  1. package com.persistence {  
  2.     package mongo {  
  3.         class MongoClient  
  4.     }  
  5.     package riak {  
  6.         class RiakClient  
  7.     }  
  8.     package hadoop {  
  9.         class HadoopClient  
  10.     }  
  11. }  

If you save this code in a file called Packages.scala and compile it using the Scala compiler (scalac Packages.scala), you’ll notice that the Scala compiler generates class files in appropriate folders to match your package declaration. This ensures that your classes are compatible with the JVM, where package declaration has to match the folder structure in the filesystem. 
 

Scala imports 
You’ve already seen some examples of import in previous chapters, but I haven’t discussed it. At first glance, the Scala import looks similar to Java imports, and it’s true they’re similar, but Scala adds some coolness to it. To import all the classes under the package com.mongodb, you have to declare the import as follows: 
  1. import com.mongodb._  
Here’s another use for _, and in this context it means you’re importing all the classes under the com.mongodb package. In Scala, import doesn’t have to be declared at the top of the file; you could use import almost anywhere: 
scala> val randomValue = { import scala.util.Random; new Random().nextInt }
randomValue: Int = 667479305

In this case you’re importing the Random class defined in the scala.util package in the Scala code block, and it’s lexically scoped inside the block and won’t be available outside it. Because the Scala package is automatically imported to all Scala programs, you could rewrite the block by relatively importing the util.Random class: 
scala> val randomValue = { import util.Random; new Random().nextInt }
randomValue: Int = 2127628205

In Scala, when you import a package, Scala makes its members, including subpackages, available to you. To import members of a class, you have to put ._ after the class name: 
scala> import java.lang.System._
import java.lang.System._

scala> nanoTime
res0: Long = 16593402215349947

Here you’re invoking the nanoTime method defined in the System class without a prefix because you’ve imported the members of the System class. This is similar to static imports in Java (Scala doesn’t have the static keyword). Because imports are relatively loaded, you could import the System class in the following way as well: 
scala> import java.lang._
import java.lang._

scala> import System._
import System._

scala> nanoTime
res1: Long = 16593550979980745

You could also list multiple imports separated by commas. Scala also lets you map a class name to another class name while importing—you’ll see an example of that soon. 

There’s one handy feature of Scala import: it allows you to control the names that you import in your namespace, and in some cases it improves readability. In Java, for example, working with both java.util.Date andjava.sql.Date in the same file becomes confusing; in Scala you could easily remap java.sql.Date to solve the problem: 
  1. import java.util.Date  
  2. import java.sql.{Date => SqlDate}  
  3. import RichConsole._  
  4.   
  5. val now = new Date  
  6. p(now)  
  7. val sqlDate = new SqlDate(now.getTime)  
  8. p(sqlDate)  
The java.sql.Date is imported as SqlDate to reduce confusion with java.util.Date. You can also hide a class using import with the help of the underscore: 
  1. import java.sql.{Date => _ }  
The Date class from the java.sql package is no longer visible for use. 

To finish the functionality required for the first user story, you still need to add methods for creating and dropping the database. To achieve that you’ll add the methods shown in the following listing. 
- Listing 3.2 Completed MongoClient 
  1. package ch3  
  2.   
  3. import com.mongodb.Mongo  
  4. import com.mongodb.DB  
  5.   
  6. class MongoClient(val host:String, val port:Int) {  
  7.   require(host != null"You have to provide a host name")  
  8.   private val underlying = new Mongo(host, port)  
  9.   def this() = this("127.0.0.1"27017)  
  10.   def dropDB(name:String) = underlying.dropDatabase(name)  
  11.   def createDB(name:String) = DB(underlying.getDB(name))  
  12.   def db(name:String) = DB(underlying.getDB(name))  
  13. }  
Everything in this code should be familiar to you except the createDB and db methods. I haven’t yet introduced DB objects (I do that in the next section). The createDB and db method implementations are identical because the getDBmethod defined in the Java driver creates a db if one isn’t found, but I wanted to create two separate methods for readability.

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!