程式扎記: [Scala IA] The Basics : Ch3. OOP in Scala - Classes and constructors

標籤

2016年7月28日 星期四

[Scala IA] The Basics : Ch3. OOP in Scala - Classes and constructors

Classes and constructors 
To connect to the already running MongoDB server, create a Mongo client class with a hostname and port number
  1. class MongoClient(val host:String, val port:Int)  
The class declaration looks different from the way you declare in Java or C#—you’re not only declaring the class, but also its primary constructor. 

The primary constructor is a constructor that needs to be called directly or indirectly from overloaded constructors when creating the instance MongoClient. You’ll look into overloaded constructors shortly. In Scala, the primary constructor for a class is coded inline with the class definition. In this case, the constructor takes two parameters: host and port. The host parameter specifies the address of the server, and port specifies the port in which the MongoDB server is waiting for the connection. 

Because all the constructor parameters are preceded by val, Scala will create immutable instance values for each of them. The following example creates an instance of a Mongo client and accesses its properties: 
scala> val client = new MongoClient("127.0.0.1", 123)
client: MongoClient = MongoClient@128d857d

scala> client.port
res0: Int = 123 
scala> client.host
res1: String = 127.0.0.1

Like Java or C#, Scala also uses the new keyword for creating instances of a class. But wait a minute—where’s the body of the MongoClient class? In Scala that’s optional. You can create classes without any class body. Creating a class like a JavaBean with only a getter and setter would be easy in Scala, as in the following: 
scala> class AddressBean(var address1:String, var address2:String, var city:String, var zipCode:Int)
defined class AddressBean

scala> var localAddress = new AddressBean("address1", "", "Taipei", 886)
localAddress: AddressBean = AddressBean@43fa0fc6

When parameters are prefixed with var, Scala creates mutable instance variables. The val and var prefixes are optional, and when both of them are missing, they’re treated as private instance values, not accessible to anyone outside the class: 



Note that when Scala creates instance values or variables, it also creates accessors for them. At no point in time are you accessing the field directly. The following MongoClient definition is equivalent to the class MongoClient(val host:String, val port:Int) definition. 

  1. class MongoClient(private val _host:String, private val _port:Int) {  
  2.     def host = _host  
  3.     def port = _port  
  4. }  
The reason I’m using private (you’ll learn about access levels later in this chapter) is so the Scala compiler doesn’t generate accessors by default. What val and var do is define a field and a getter for that field, and in the case of varan additional setter method is also created. 


Most of the time you’ll have MongoDB running on the localhost with default port 27017. Wouldn’t it be nice to have an additional zero-argument constructor that defaults the host and port number so you don’t have to specify them every time? How about this: 
  1. class MongoClient(val host:String, val port:Int) {  
  2.     def this() = this("127.0.0.1"27017)  
  3. }  
To overload a constructor, name it this followed by the parameters. Constructor definition is similar to method definition except that you use the name this. Also, you can’t specify a return type as you can with other methods. The first statement in the overloaded constructors has to invoke either other overloaded constructors or the primary constructor. The following definition will throw a compilation error: 
  1. class MongoClient(val host:String, val port:Int) {  
  2.     def this() = {  
  3.         val defaultHost = "127.0.0.1"  
  4.         val defaultPort = 27017  
  5.         this(defaultHost, defaultPort)  
  6.     }  
  7. }  
When you compile this with scalac, you get the following compilation errors: 


This poses an interesting challenge when you have to do some operation before you can invoke the other constructor. Later in this chapter, you’ll look into a companion object and see how it addresses this limitation. To make a connection to the MongoDB you’ll use the com.mongodb.Mongo class provided by the Mongo Java driver: 
  1. package ch3  
  2.   
  3. import com.mongodb.Mongo  
  4.   
  5. class MongoClient(val host:String, val port:Int) {  
  6.   private val underlying = new Mongo(host, port)  
  7.   def this() = this("127.0.0.1"27017)  
  8. }  
The underlying instance value will hold the connection to MongoDB. When Scala generates the constructor, it instantiates the underlying instance value too. Because of Scala’s scripting nature, you can write code inside the class like a script, which will get executed when the instance of the class is created (kind of like Ruby). The following example creates a class called MyScript that validates and prints the constructor input: 
  1. class MyScript(host:String) {  
  2.   require(host != null"Have to provide host name")  
  3.   if(host == "127.0.0.1") println("host = localhost")  
  4.   else println("host = " + host)  
  5. }  
And now load MyScript into Scala REPL: 
scala> :load MyScript.scala
Loading MyScript.scala...
defined class MyScript


scala> val s = new MyScript("127.0.0.1")
host = localhost
s: MyScript = MyScript@1f119598


scala> val s = new MyScript(null)
java.lang.IllegalArgumentException: requirement failed: Have to provide host name
at scala.Predef$.require(Predef.scala:224)

How is Scala doing this? Scala puts any inline code defined inside the class into the primary constructor of the class. If you want to validate constructor parameters, you could do that inside the class (usually at the top of the class). Let’s validate the host in the MongoClient
  1. package ch3  
  2.   
  3. import com.mongodb.Mongo  
  4.   
  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. }  
Right now the MongoClient is using an underlying instance to hold the connection to MongoDB. Another approach would be to inherit from the com.mongodb.Mongo class, and in this case you don’t have to have any instance value to hold the connection to MongoDB. To extend or inherit from a superclass, you have to use the extends keyword. The following code demonstrates how it would look if you extended from the Mongo class provided by the Java driver: 
  1. package ch3  
  2.   
  3. import com.mongodb.Mongo  
  4.   
  5. class MongoClientV2(val host:String, val port:Int)   
  6.   extends Mongo(host, port){  
  7.   require(host != null"You have to provide a host name")  
  8.   def this() = this("127.0.0.1"27017)  
  9. }  
As shown in the previous example, you can also inline the definition of the primary constructor of a superclass. One drawback of this approach is that you can no longer validate the parameters of the primary constructor before handing it over to the superclass. 
NOTE. 
When you don’t explicitly extend any class, by default that class extends the scala.AnyRef class. scala.AnyRef is the base class for all reference types (see section 3.1).

Even though extending com.mongodb.Mongo as a superclass is a completely valid way to implement this driver, you’ll continue to use the earlier implementation because that will give you more control over what you want to expose from the Scala driver wrapper, which will be a trimmed-down version of the complete Mongo Java API. Before going any further, I’ll talk about Scala imports and packages. This will help you to work with the Mongo library and structure your code.

沒有留言:

張貼留言

網誌存檔