程式扎記: [Scala IA] The Basics : Ch3. OOP in Scala - Objects and companion objects

標籤

2016年8月7日 星期日

[Scala IA] The Basics : Ch3. OOP in Scala - Objects and companion objects

Objects and companion objects 
Before I show you the DB class used in the previous example, let’s explore Scala objects. Scala doesn’t provide any static modifier, and that has to do with the design goal of building a pure object-oriented language where every value is an object, every operation is a method call, and every variable is a member of some object. Having static doesn’t fit well with that goal, and along with that there are plenty of downsides to using static in the code. Instead, Scala supports something called Singleton objects. A singleton object allows you to restrict the instantiation of a class to one object. Implementing a singleton pattern in Scala is as simple as the following: 
  1. object RichConsole {  
  2.     def p(x: Any) = println(x)  
  3. }  
Here RichConsole is a singleton object. The object declaration is similar to a class declaration except instead of class you’re using the object keyword. To invoke the new p method, you have to prefix it with the class name, as you’d invoke static methods in Java or C#: 
scala> :load RichConsole.scala
Loading RichConsole.scala...
defined object RichConsole


scala> RichConsole.p("rich console")
rich console

You can import and use all the members of the RichConsole object as follows: 
scala> import RichConsole._
import RichConsole._ 
scala> p("this is cool")
this is cool

The DB object introduced in listing 3.2 is nothing but a factory to create DB instances representing a database in MongoDB: 
  1. object DB {  
  2.     def apply(underlying: MongDB) = new DB(underlying)  
  3. }  
What’s interesting here is that when you use a DB object as a factory, you’re calling it as if it’s a function, DB(underlying.getDB(name)), whereas you’d expect something like DB.apply(underlying.getDB(name)). Scala provides syntactic sugar that allows you to use objects as function calls. Scala achieves this by translating these calls into the apply method, which matches the given parameters defined in the object or class. If there’s no matching apply method, it will result in a compilation error. Even though calling an apply method explicitly is valid, a more common practice is the one I’m using in the example. Note also that an object is always evaluated lazily, which means that an object will be created when its first member is accessed. In this case, it’s apply
The Factory pattern in Scala 
When discussing constructors I mentioned that sometimes working with constructors could create some limitations like processing or validating parameters because in overloaded constructors the first line has to be a call to another constructor or the primary constructor. Using Scala objects we could easily address that problem because the apply method has no such limitation. For example, let’s implement a Factory pattern in Scala. Here you’ll create multiple Role classes, and based on the role name you’ll create an appropriate role instance:
  1. abstract class Role { def canAccess(page: String): Boolean }  
  2. class Root extends Role {  
  3.     override def canAccess(page:String) = true  
  4. }  
  5. class SuperAnalyst extends Role {  
  6.     override def canAccess(page:String) = page != "Admin"  
  7. }  
  8. class Analyst extends Role {  
  9.     override def canAccess(page:String) = false  
  10. }  
  11. object Role {  
  12.     def apply(roleName:String) = roleName match {  
  13.         case "root" => new Root  
  14.         case "superAnalyst" => new SuperAnalyst  
  15.         case "analyst" => new Analyst  
  16.     }  
  17. }  
Now you can use the Role object as a factory to create instances of various roles:
scala> val root = Role("root")
root: Role = Root@5a4be131

scala> val analyst = Role("analyst")
analyst: Role = Analyst@1f907e8e

Inside the apply method you’re creating an instance of the DB class. In Scala, both a class and an object can share the same name. When an object shares a name with a class, it’s called a companion object, and the class is called a companion class. Now the DB.scala file looks like the following: 
  1. package ch3  
  2.   
  3. import com.mongodb.{DB => MongoDB}  
  4.   
  5. class DB private(val underlying: MongoDB) {  
  6.     
  7. }  
  8.   
  9. object DB {  
  10.   def apply(underlying: MongoDB) = new DB(underlying)  
  11. }  
First, the DB class constructor is marked as private so that nothing other than a companion object can use it. In Scala, companion objects can access private members of the companion class, which otherwise aren’t accessible to anything outside the class. In the example, this might look like overkill, but there are times when creating an instance of classes through a companion object is helpful (look at the sidebar for the factory pattern). The second interesting thing in the previous code is the mongodb import statement. Because of the name conflict, you’re remapping the DB class defined by the Java driver to MongoDB


In MongoDB, a database is divided into multiple collections of documents. Shortly you’ll see how you can create a new collection inside a database, but for now add a method to retrieve all the collection names to the DB class: 
  1. package ch3  
  2.   
  3. import com.mongodb.{DB => MongoDB}  
  4. import scala.collection.convert.Wrappers._  
  5.   
  6. class DB private(val underlying: MongoDB) {  
  7.    def collectionNames = for(name <- nbsp="" span="">new JSetWrapper(underlying.getCollectionNames)) yield name  
  8. }  
  9. ...  
The only thing that looks somewhat new is the Wrappers object. You’re using utility objects provided by Wrappers to convert a java.util.Set to a Scala set so you can use a Scala for-comprehension. Wrappers provides conversions between Scala and Java collections. To try out the mongodb driver, write this sample client code: 
  1. import com.scalainaction.mongo._  
  2. def client = new MongoClient  
  3. def db = client.createDB("mydb")  
  4. for(name <- db.collectionnames="" name="" nbsp="" println="" span="">
This sample client creates a database called mydb and prints the names of all the collections under the database. If you run the code, it will print test and system.indexes, because by default MongoDB creates these two collections for you. Now you’re going to expose CRUD (create, read, update, delete) operations in the Scala driver so that users of your driver can work with documents. The following listing shows the Scala driver code you’ve written so far. 
- Listing 3.3 Completed MongoClient.scala 
  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.   def dropDB(name:String) = underlying.dropDatabase(name)  
  10.   def createDB(name:String) = DB(underlying.getDB(name))  
  11.   def db(name:String) = DB(underlying.getDB(name))  
  12. }  
Using MongoClient, your driver will be able to connect to the running MongoDB server, to a given host and port, or to the local MongoDB server. You also added methods like dropDBcreateDB, and db to manage MongoDB databases. The following listing shows the DB class you created to wrap the underlying MongoDB database. 
- Listing 3.4 DB.scala 
  1. package ch3  
  2.   
  3. import com.mongodb.{DB => MongoDB}  
  4. import scala.collection.convert.Wrappers._  
  5.   
  6. class DB private(val underlying: MongoDB) {  
  7.    def collectionNames = for(name <- nbsp="" span="">new JSetWrapper(underlying.getCollectionNames)) yield name  
  8. }  
  9.   
  10. object DB {  
  11.   def apply(underlying: MongoDB) = new DB(underlying)  
  12. }  
So far, you haven’t added much functionality to the DB class. The only thing it provides is an easier way to access names of the collections of a given database. But that’s about to change with Scala traits.

沒有留言:

張貼留言

網誌存檔

關於我自己

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