2016年8月7日 星期日

Objects and companion objectsBefore 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:

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#:

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:

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:

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:

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:

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

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 dropDB, createDB, 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

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.