Scala开发教程(41): 组合和继承–定义factory对象

jerry Scala 2015年11月25日 收藏

到目前为止,我们定义了关于布局元素类的一个层次结构。你可以把包含这个层次关系的类作为API接口提供给其它应用,但有时你可以希望对函数库的用户隐藏这种层次关系,这通常可以使用factory(构造工厂)对象来实现。一个factory对象定义了用来构造其它对象的函数。库函数的用户可以通过工厂对象来构造新对象,而不需要通过类的构造函数来创建类的实例。使用工厂对象的好处是,可以统一创建对象的接口并且隐藏被创建对象具体是如何来表示的。这种隐藏可以使得你创建的函数库使用变得更简单和易于理解,也正是隐藏部分实现细节,可以使你有机会修改库的实现而不至于影响库的接口。
实现factory对象的一个基本方法是采用singleton模式,在Scala中,可以使用类的伴随对象(companion 对象)来实现。比如:

object Element {
  def elem(contents: Array[String]):Element =
   new ArrayElement(contents)

  def elem(chr:Char, width:Int, height:Int) :Element =
    new UniformElement(chr,width,height)

  def elem(line:String) :Element =
    new LineElement(line)
}

我们先把之前Element的实现列在这里:

abstract class Element {
  def contents: Array[String]
  def height: Int = contents.length
  def width: Int = if (height == 0) 0 else contents(0).length
  def above(that: Element) :Element =
    new ArrayElement(this.contents ++ that.contents)
  def beside(that: Element) :Element = {
    new ArrayElement(
      for(
        (line1,line2) <- this.contents zip that.contents
      ) yield line1+line2
    )
  }
  override def toString = contents mkString "\n"

}

有了object Element(类Element的伴随对象),我们可以利用Element对象提供的factory方法,重新实现类Element的一些方法:

abstract class Element {
  def contents: Array[String]
  def height: Int = contents.length
  def width: Int = if (height == 0) 0 else contents(0).length
  def above(that: Element) :Element =
    Element.elem(this.contents ++ that.contents)
  def beside(that: Element) :Element = {
    Element.elem(
      for( 
        (line1,line2) <- this.contents zip that.contents
      ) yield line1+line2
    ) 
  }
  override def toString = contents mkString "\n"

}

这里我们重写了above和beside方法,使用伴随对象的factory方法Element.elem替代new 构造函数。
这样修改之后,库函数的用户不要了解Element的继承关系,甚至不需要知道类ArrayElement,LineElement定义的存在,为了避免用户直接使用ArrayElement或LineElement的构造函数来构造类的实例,因此我们可以把ArrayElement,UniformElement和LineElement 定义为私有,定义私有可以也可以把它们定义在类Element内部(嵌套类)。下面为这种方法的使用:

object Element {

  private class ArrayElement(val contents: Array[String])
    extends Element {
  }

  private class LineElement(s:String) extends ArrayElement(Array(s)) {
    override def width = s.length
    override def height = 1
  }

  private class UniformElement (ch :Char,
    override val width:Int,
    override val height:Int
  ) extends Element{
    private val line=ch.toString * width
    def contents = Array.fill(height)(line)
  }
  
  def elem(contents: Array[String]):Element =
   new ArrayElement(contents)

  def elem(chr:Char, width:Int, height:Int) :Element =
    new UniformElement(chr,width,height)

  def elem(line:String) :Element =
    new LineElement(line)
}