Scala学习笔记(3): 面向对象与容器

面向对象编程

这个通过一个例子来解释Scala面向对象编程的基本概念,这里我们需要为MongoDB(一种NoSQL数据库,详见我的另一篇博客)编写一个简单的API。MongoDB官方提供了Java API,而我们要做的是使用Scala对其进行简单的封装。

Class

先来搭建一个叫MongoClient的类作为建立数据库连接的入口,定义一个MongoClient类,并指定主构造函数的两个参数(val host: String, val port: Int)。

class MongoClient(val host: String, val port: Int)

构造函数参数的格式可以是[val/var] param : type,区别在于:val参数是只读的(相当于只有get方法),var参数是可写的(有get和set方法),如果没有前缀则是私有的(没有get和set方法)

构造函数

可以在类中定义多个构造函数(包括默认构造函数),例如下面是MongoClient的一个完整定义:

class MongoClient(val host: String, val port: Int) {
  
  require(host != null, "You have to provide a host name")
  private val underlying = new Mongo(host, port)

  def this() = this("127.0.0.1", 27017)
  def version = underlying.getVersion()
  def dropDB(name: String) = underlying.dropDatabase(name)
  def createDB(name: String) = DB(underlying.getDB(name))
  def db(name: String) = DB(underlying.getDB(name))
}

def this() 定义了一个默认构造函数。

Case class

case class是一种特殊的Class,编译器会为其自动生成一些样板代码(equals, hashCode, toString, apply等)。

case class Person(firstName:String, lastName:String)

Object

Scala中不存在静态变量和静态方法,所有的变量和方法必须定义在类或者定义在object中,所谓的object就是一个单例对象。

object可以用来定义一个可执行类:

object HelloWrold extends App {
    println("Hello World")
}

object可以定义

object的另一种常见的用法是作为一个类的伴随对象,用来定义一些“静态”的工厂函数。例如下面这段程序中,我们首先定义了DB类,然后定义了一个同样叫DB的伴随对象,伴随对象可以可以访问伴随类的私有成员。

class DB private (val underlying: MongoDB){ //注意private关键词,DB的构造函数是私有的
  def collectionNames = for(name <- new JSetWrapper(underlying.getCollectionNames())) yield name
  
  private def collection(name: String) = underlying.getCollection(name)
  
  def readOnlyCollection(name: String) = new DBCollection(collection(name))
  def administrableCollection(name: String) = new DBCollection(collection(name)) with Administrable
  def updatableCollection(name: String) = new DBCollection(collection(name)) with Updatable 
}

object DB {
  def apply(underlying: MongoDB) = new DB(underlying)
}

包与引入

Scala中包也是一种特殊的对象,在包内部可以看定义其他类与对象,如果要在包里面定义函数或者变量的话,请使用包对象:

//package.scala
package object bar {
  val minimumAge = 18
  def verifyAge = {}
}

//other.scala
package bar
class BarTender {
  def serveDrinks = { verifyAge; ... }
}

包的定义可以是嵌套式的(C#中常见),或者是平板式的(Java中常见)。引用包的常见写法有:

import com.mongodb.DBObject
import com.mongodb._  //_与Java中的×(星号)是一个意思
import com.mongodb.{DBCollection => MongoDBCollection} //引入包后更改其名称

Scala的继承模型

scala.Any是任何类的父类;Scala.AnyRef相当于Java中的Object类,即任何引用类的父类;AnyVal是任何值类的父类。

在图中实线表示继承关系,虚线表示view关系,view的意思是类型间存在隐式转换。

在类层级的最底下是Scala.Null和Scala.Nothing,Scala.Null类型只有一个实例化对象null, Scala.Nothing则不能实例化。

特质

Trait可以翻译成特质,你可以认为特质是一种增强版的接口,但我认为把它理解为一种部分实现的类更为合适。为什么这么说?因为使用特质构造程序,允许我们把一个大的类打散成多个特质,相当于把机器拆成零件,然后再自由组装。

特性 Interface (Java) Abstract class (Java + Scala) Trait
实例化 No No No
构造参数 No Yes No
方法定义 Yes Yes Yes
方法实现 No Yes Yes
成员变量 No Yes Yes
多重组合(继承) Yes No Yes

例如下面这个例子中,我们把mongodb java API的功能打散成几个不同的特质,所以特质都从ReadOnly特质继承而来。

trait ReadOnly {
  val underlying : MongoDBCollection
  
  def name = underlying getName
  def fullName = underlying getFullName
  def find(doc: DBObject) = underlying find doc
  def findOne(doc: DBObject) = underlying findOne doc
  def findONe = underlying findOne
  def getCount(doc: DBObject) = underlying getCount doc
  
  def find(query: Query): DBCursor = {
    def applyOptions(cursor:DBCursor, option: QueryOption): DBCursor = {
    option match {
      case Skip(skip, next) => applyOptions(cursor.skip(skip), next)
      case Sort(sorting, next)=> applyOptions(cursor.sort(sorting), next)
      case Limit(limit, next) => applyOptions(cursor.limit(limit), next)
      case NoOption => cursor
    }
    }
    applyOptions(find(query.q), query.option)
  }
}

trait Administrable extends ReadOnly {
  
  def drop: Unit = underlying drop
  def dropIndexes : Unit = underlying dropIndexes
  
}

trait Updatable extends ReadOnly {
  def -=(doc: DBObject): Unit = underlying remove doc
  def +=(doc: DBObject): Unit = underlying save doc
}

trait LocaleAware extends ReadOnly {
  override def findOne(doc: DBObject) = {
    doc.put("locale", java.util.Locale.getDefault.getLanguage)
    super.findOne(doc)
  }
  override def find(doc: DBObject) = {
    doc.put("locale", java.util.Locale.getDefault.getLanguage)
    super.find(doc)
  }
}

class DBCollection(override val underlying: MongoDBCollection) extends ReadOnly

//用法
new DBCollection(mongoDBCollection) with Updatable with Administrable

Scala容器

Scala中容器被划分为两类:immutable和mutable,分别属于包 scala.collection.immutable和 scala.collection.mutable。Java程序员可以用String和StringBuffer的来理解它们的区别,即immutable容器上的增删操作会返回一个新的immutable容器,而mutable容器上的增删操作是in place(会产生副作用)。

所有immutable和mutable容器都继承自scala.collection里定义的抽象类,它的类结构如下:

  • IndexedSeq的实现是Vector, LinearSeq的实现是list
  • 通过使用隐式转换,Java中的String和Array类型在Scala中“继承”了IndexedSeq接口

常见的collection操作

  • map 使用函数f(T=>T)将collection中的元素映射到一个新的collection
  • flatMap 函数映射f(T=>List[T]),但会把collection扁平化
  • filter
  • foldLeft
  • foldRight

并发容器

Scala 2.9引入了并发容器,使得很多容器操作可以安全的并发执行。例如,在普通容器类后加.par后缀即可转换为并发容器。

myCollection.par.foldLeft(0)((a,b) => a+b)

Par容器的内部实现是基于类似于MergeSort的分割组合算法。


你可能感兴趣的:(Scala学习笔记(3): 面向对象与容器)