Scala中的组合与继承----建立一个创建和渲染二维布局元素的库(实例)

一、关于抽象类

在这个实例中,我们的第一项任务就是定义代表布局元素的类型Element。因为元素是二维的字符矩形,所以顺理成章应包含一个成员contents指向布局元素的内容。内容可以由字符串数组表示,这里每个字符串代表一行。因此,contents返回的结果类型为Array[String]。如:

abstract class Element{
	def contents:Array[String]
}	

这个类里,contents被声明为没有实现的方法。换句话说,这个方法是Element类的抽象成员。具有抽象成员的类本身被声明为抽象的,只要在class关键字之前加上abstract修饰符即可。
abstract修饰符说明类可能有未来实现的成员,因此不能实例化一个抽象类。如果尝试这么做那就回报错。
注意
一个方法只要没有实现(即没有等号或方法体),他就是抽象的。不像Java,方法的声明中不需要也不允许有抽象修饰符。而拥有实现的方法则被称为具体的方法。

二、定义无参方法

接下来,我们将向Element添加显示宽度和高度的方法。

abstract class Element{
 def contents:Array[String]
 def height:Int = contents.length
 def width:Int = if(height == 0 ) 0 else contents(0).length
 

height方法返回contents里的行数。width方法返回第一行的长度,如果元素没有行则返回0。(即不能定义一个高度为0但宽度不为0的元素。
将width和height作为字段而不是方法来实现:简单地在每个实现里把def修改成val即可

abstract class Element{
 def contents:Array[String]
 val height = contents.length
 val width =
  if(height == 0) 0 else contents(0).length
}  

访问字段比调用方法略快。
Scala在遇到混合了五参数和空括号方法的情况时很自由。特别是,你可以用空括号方法重写无参数方法,并且反之也可以。你还可以在调用任何不带参数的方法时省略空的括号。
原则上,Scala的函数调用中可以省略所有的 空括号,然而在调用的方法超出其调用者对象的属性时,推荐仍然写一对空的括号。

三、扩展类

我们仍然需要创建新的元素对象,你已经看到了因为Element类是抽象的,所以“new Element”做不了这件事。因此为了实例化一个元素,我们需要创建一个扩展了Element类并实现抽象contents方法的子类。

class ArrayElement(conts:Array[String]) extends Element{
 def contents:Array[String] = conts
 }

extends的两个效果:

  1. 使得ArrayElement类继承Element类的所有非私有成员
  2. 让ArrayElement类型成为Element类型的子类

继承表示超类的所有成员也是子类的成员。以下两种情况例外:

  1. 超类的私有成员不会被子类继承
  2. 超类中的成员与子类中实现的成员具有相同名称和参数则不会被子类继承

这时候,被称为子类的成员重写了超类的成员。如果说子类中的成员是具体的而超类中的是抽象的,我们也可以说具体的成员实现了抽象的成员。

四、重写方法和字段

统一访问原则只是Scala在对待字段和方法上比Java更统一的一个方面。另一个差异是Scala里的字段和方法属于相同的命名空间。这让字段可以重写无参数方法。如将上述示例从方法变为字段

class ArrayElement(conts:Array[Strin]) extends Element {
 val contents:Array[String] = conts
 }

五、定义参数化字段

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

这样定义可以避免某些不必要的累赘和重复
类的参数同样也可以使用var做前缀,这种情况下相应的字段可以被重新赋值。最后,与其他类成员相同,这些参数化字段还可以添加各种权限修饰符。
例如

class  Cat{
val dangerous = false
}
class Tiger{
override val dangerous:Boolean
private var age:Int
}extends Cat

六、调用超类构造器

面向对象编程让使用新的数据类型来扩展系统变得容易。加入子类即可。

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

由于LineElement扩展了ArrayElement,并且ArrayElement的构造器带一个参数(Array[String]),因此LineElement需要传给超类的主构造器一个参数。要调用超类构造器,只要 简单地把要传递的参数或参数列表放在超类名之后的括号里即可。

七、使用override修饰符

若成员实现的是同名的抽象成员时,则这个修饰符是可选的;若成员并未重写或实现什么其他基类里的成员则禁用这个修饰符。由于LineElement类的height和width重写了Element类的具体成员定义,因此需要override修饰符。

八、多态和动态绑定

多态:父类变量指定子类类型对象
在多态这种情况下,Element对象可以有许多形式。可以通过定义新的Element子类创造Element的更多形式。
例如:给出定义拥有给定长度和高度并充满指定字符的新的Element形式。

class UniformElement(
 ch:Char,
 override val width:Int,
 override val height:Int
 )extends Element {
  private val line = ch.toString * wodth
  def contents = Array.make(height,line)
  }

布局元素的类层级布局元素的类层级

变量和表达式上的方法是动态绑定的。这意味着被调用的实际方法取决于运行期对象基于的类而不是变量或表达式的类型。

九、定义final成员

有时在设计一个继承层级时,你想要确保一个成员不被子类重写。这在Scala中可以和Java一样通过给成员添加final修饰符来实现。
例如:

class ArrayElement extends Element{
 final override def demo() {
  println("ArrayElement's implementation invoked")
  }
 }

如果即使这样你还想确保整个类都不会有子类。那么就在类的声明上添加final修饰符来把整个类声明为final即可。

十、使用组合与继承

组合与继承是利用其它现存类定义新类的两个方法。如果追求的根本上的代码重用,那么更推荐采用组合。而继承受累与脆基类的问题,因为可能会在更改超类时无意中破坏子类。
在我们的案例中,把LineElement定义为Element的直接子类会更好一些。如:

class LineEelement(s:String) extends Element{
  val contents = Array(s)
  override def width = s.length
  override def height = 1 
 } 

现在的LineElement与ArrayElement是组合关系。

十一、实现above、beside和toString

利用above的方法简单实现将Element中连接两个元素的contents值。

def above(that:Element):Element = 
  new ArrayElement(this.contents ++ that.contents)

++操作符连接两个数组。
实现方法beside

def beside (that:Element):Element = {
  val contents = new Array[String](this.contents.length)
  for(i<- 0 until this.contents.length)
 	contents(i) =  this.contents(i) + that.contents(i))
 new ArrayElement(contents)
  }

因为beside是指令式风格的。我们可以将其缩减成一个表达式

new ArrayElement(
   for(
      (line1,line2) <- this.contents zip that.contents
    )yield line1+line2  

这里,this.contents和that.contents两个数组用zip操作符转换为一个二元对的数组(称为Tuple2)。zip方法从它的两个参数中取出相应的元素并生成二元对数组。如果两个数组中的一个比另一个长,zip将舍弃多余的元素。for表达式的yield部分可以产生结果。结果与枚举遍历大表达式类型一致,也就是说,是一个数组。数组的每个元素都是相应行,即line1和line2串联的结果。因此这段代码最终的结果与前一个版本的beside一样,不过它避免了显示的数组索引,因此出错的机会更少。

通过定义toString方法返回元素格式化成的字符串

override def toString = contents mkString "\n"

十二、定义工厂对象

工厂对象包含了构建其他对象的方法。客户将使用这些方法构造对象而不是直接使用new构造对象。这种方式的一个好处是可以将对象的创建集中化并且隐藏对象实际代表的类的细节。
布建构造方法的首要任务
选择工厂发方法应该放在何处,应该是单例对象还是类成员,应该怎么调用包含他们的对象或类。
一是创建类的伴生对象并把它作为布局元素的工厂方法
二是将Element的子类放在Element单例对象中并在那里声明它们为私有。

十三、变高变宽

为了让不同宽度的元素堆叠在一起,或者把不同高度的元素靠在一起。使用私有助手方法widen,能够以指定宽度为参数并返回宽度的Element返回结果包括这个Element的居中显示的内容和为满足宽度要求在左侧和右侧留的空格。
添加highten方法,能在竖直方向执行同样的功能。widen方法被above调用以确保Element堆叠在一起后有同样的宽度。类似的,heighten方法被beside调用以确保靠在一起的元素具有同样的高度。

十四、完全代码呈现

package test2

import javassist.bytecode.stackmap.TypeData.ArrayElement
import Element.elem

object Element {
  private class ArrayElement (
    val contents:Array [String]
    ) extends Element
  private class LineElement(s: String) extends Element {
    val contents = 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()
  }
  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)
}
abstract class Element {
  def contents: Array[String]

  def width: Int = contents(0).length

  def height: Int = contents.length

  def above(that: Element): Element = {
    val this1 = this widen that.width
    val that1 = that widen this.width
    elem(this1.contents ++ that1.contents)
  }

  def beside(that: Element): Element = {
    val this1 = this heighten that.height
    val that1 = that heighten this.height
    elem(
      for ((line1, line2) <- this1.contents zip that1.contents)
        yield line1 + line2)
  }

  def widen(w: Int): Element = {
    if (w <= width) this
    else {
      val left = elem(' ',(w-width)/ 2,height)
      var right = elem(' ',w-width-left.width, height)
      left beside this beside right
    }
  }

  def heighten(h: Int): Element = {
    if (h <= height) this
    else {
      val top = elem(' ',width,(h - height)/2)
      var bot = elem(' ',width, h- height - top.height)
      top above this above bot
    }
  }

  override def toString = contents mkString "\n"
}

object Spiral {
  val space = elem(" ")
  val corner = elem("+")

  def spiral(nEdges: Int, direction: Int): Element = {
    if (nEdges == 1)
      elem("+")
    else {
      val sp = spiral(nEdges - 1, (direction + 3) % 4)

      def verticalBar = elem('|', 1, sp.height)

      def horizontalBar = elem('-', sp.width, 1)

      if (direction == 0)
        (corner beside horizontalBar) above (sp beside space)
      else if (direction == 1)
        (sp above space) beside (corner above verticalBar)
      else if (direction == 2)
        (space beside sp) above (horizontalBar beside corner)
      else
        (verticalBar above corner) beside (space above sp)

    }
  }

  def main(args: Array[String]) {
    val nSides = args(0).toInt
    println(spiral(nSides, 0))
  }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值