scala 组合和继承(一)- 8

概述

定义一个新类主要有两种模式:一个通过组合的方式,新创建的类通过引用其他类的组合而成,一个是通过组合的方式

假设需要定义一个函数库。这个库用来定义在平面上(二维空间)的布局元素,每个元素使用一个含有文字的矩形来表示。

首先定义一个类构造工厂方法elem,根据传入的参数来创建一个布局元素。

elem(s: String):Element

布局元素使用类型Element来构造其模型

组合和继承

抽象类

首先定义Element类型,用来表示一个布局元素,定义一个成员变量contents,用它来表示这个二维布局元素的内容。

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

成员contents使用了没有定义具体实现的方法来实现,这个方法被称为抽象方法。一个含有抽象方法的类必须被定义为抽象类,也就是使用abstract来定义类

无法构建抽象类的实例

scala> new Element
<console>:9: error: class Element is abstract; cannot be instantiated
              new Element

与java不同的是,抽象方法不需要abstract修饰符来表示,一个没有定义实现的方法就是抽象方法,如果该方法有具体实现,则称为具体方法。

无参数方法

height方法返回contents的行数,width方法返回第一行的长度

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

注意上面三个方法都是无参数方法。相对的,带有空括号的方法定义,如 def height():Int,就是空括号方法

方法在不需要参数并且只是读取对象状态时,使用无参数方法

也可以使用成员变量来定义width和height

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

实现是等价的,但是成员变量的方法会更快一点,因为方法调用的时候都要计算字段值。

scala在调用不带参数的方法时省略括号

Array(1, 2, 3).toString 
"abc".length

原则上,scala可以省略所有的空括号

拓展类

关键字 extends

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

重载成员函数和方法

和 Java 稍有不同的一点是,Scala 中成员函数和成员变量地位几乎相同,而且也处在同一个命名空间。也就是说,Scala 中不允许定义同名的成员函数和成员变量,但带来的一个好处是,可以使用成员变量来重载一个不带参数的成员函数。比如,接着前面的例子,你可以通过一个成员变量来实现基类中定义的抽象函数 contents 。

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

可以看到,使用成员变量来实现基类中不带参数的抽象函数,是一个非常恰当的例子。Scala 中的这种实现是 Java 语言所不支持的,一般来说只有两个不同的命名空间来定义类,而 Java 可以有四个, Scala 支持的两个命名空间如下:

值(字段,方法,包还有单例对象)
类型(类和 Trait 名)
Scala 把字段和方法放进同一个命名空间的理由很清楚,因为这样做,你就可以使用 val 重载无参数的方法。

定义参数化成员变量

我们回到前面定义的类 ArrayElement,它有一个参数 conts ,其唯一的目的,是用来复制到 contents 成员变量。而参数名称 conts 是为了让它看起来和成员变量 contents 类似,而又不至于和成员变量名冲突。

Scala 支持使用参数化成员变量,也就是把参数和成员变量定义合并到一起来避免上述冲突:

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

要注意的是,现在参数 contents 前面加上了 val 关键字,这是前面使用同名参数和同名成员变量的一个缩写形式。使用 val 定义了一个无法重新赋值的成员变量。这个成员变量初始值为参数的值,可以在类的外面访问这个成员变量。它的一个等效的实现如下:

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

Scala 也允许你使用 var 关键字来定义参数化成员变量,使用 var 定义的成员变量,可以重新赋值。

此外,Scala 也允许你使用 private、protected 和 override 来修饰参数化成员变量。这与你定义普通的成员变量的用法一样。比如:

class Cat {
  val dangerous =false
}

class Tiger (
  override val dangerous: Boolean,
  private var age: Int
) extends Cat

这段代码中 Tiger 的定义其实为下面类定义的一个缩写:

class Tiger(param1: Boolean, param2: Int) extends Cat { 
    override val dangerous = param1 
    private var age = param2 
}

调用基类构造函数

前面我们定义了两个类,一个为抽象类 Element ,另外一个为派生的实类 ArrayElement 。或许你打算再构造一个新类,这个类使用单个字符串来构造布局元素,使用面向对象的编程方法使得构造这种新类非常容易。比如下面的 LineElement 类: 注意:这里重载方法,需要使用前面的方法而不是val的类,可以再次定义类:

abstract class Element { 
  def contents: Array[String] 
  def height: Int = contents.length 
  def width: Int = if (height == 0) 0 else contents(0).length 
}
class ArrayElement(conts: Array[String]) extends Element {
  def contents: Array[String] = conts
}
class LineElement(s:String) extends ArrayElement(Array(s)) {
  override def width = s.length
  override def height = 1
}

由于 LineElement 扩展了 ArrayElement ,并且 ArrayElement 的构造器带一个参数(Array[String])。LineElement 需要传递一个参数到它的基类的主构造器。要调用基类构造器,只要把你要传递的参数或参数列表放在基类名之后的括号里即可。例如,类 LineElement 传递了 Array(s) 到 ArrayElement 的主构造器,把它放在基类 ArrayElement 的名称后面的括号里:

... extends ArrayElement(Array(s)) ...

多态和动态绑定

在前面的例子中,我们看到类型为 Element 的变量可以保存 ArrayElement 类型的对象,这种现象称为“多态”。也就是说,基类类型的变量可以保存其子类类型的对象。到目前为止,我们定义了两个 Element 的子类, ArrayElement 和 LineElement 。你还可以定义其它子类,比如:

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)
}

Scala将接受下列所有的赋值,因为赋值表达式的类型符合定义的变量类型:

val e1: Element = new ArrayElement(Array("hello", "world")) 
val ae: ArrayElement = new LineElement("hello") 
val e2: Element = ae
val e3: Element = new UniformElement('x', 2, 3)

若你检查继承层次关系,你会发现:这四个 val 定义的每一个表达式,等号右侧表达式的类型都在被初始化的等号左侧的 val 类型的层次之下。

另一方面,如果调用变量(对象)的方法或成员变量,这个过程是一个动态绑定的过程。也就是说,调用哪个类型的方法,取决于运行时变量当前的类型,而不是定义变量的类型。

final成员

  • 不希望基类的某些成员被子类重载
class ArrayElement extends Element { 
  final override def demo() { 
    println("ArrayElement's implementation invoked") 
  } 
}
  • 不希望某个类派生子类
final class ArrayElement extends Element { 
   override def demo() { 
    println("ArrayElement's implementation invoked") 
  } 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值