第10章 组合和继承

第10章 组合和继承

组合的意思是一个类可以包含对另一个类的引用,利用这个被引用类来帮助他完成任务,而继承是超类/子类的关系

1 一个二维的布局类库
2 抽象类
abstract class Element {
  def contents: Array[String];
}
  • 一个方法只要没有实现,那么它就是抽象的,跟 Java 不同并不需要(也不能)在方法上加上 abstract 修饰符
  • 一个包含抽象成员的类本身也要声明为抽象的,做法是在 class 关键字前加上 abstract 修饰符
3 定义无参方法
abstract class Element {
  def contents: Array[String]
  //无参方法,方法没有圆括号	
  def width: Int = contents.length
  //scala的统一访问原则:可以将height与width实现为字段,只需要将def改成val	
  def height: Int = if (contents.length == 0) 0 else contents(0).length
}
  • width 与 height 方法并不带圆括号,这样的无参方法在scala中很常见
  • scala 统一访问原则:使用方代码不应受到某个属性是用字段还是用方法实现的影响 。举例来说,完全可以把width和height实现为字段,而不是方法,只要将定义的def 换成 val 即可,唯一的区别是字段访问可能比方法调用略快,因为字段值在类初始化时被预先计算好,而不是每次方法调用时重写计算,另一方面,字段需要每个对象为其分配额外的内存空间,因此属性实现为字段还是方法好,这个问题取决于类的用法,而用法可以随着时间的变化而变化
  • java 并没有实现统一访问原则,java 中要写成 string.length() 而不是 string.length,而对于数组要写成 array.length,而不是array.length() 。为了更好的桥接这两种写法,scala 可以用空括号方法重写无参方法,反过来也可以。
  • scala 鼓励我们将那些不接受参数也没有副作用的方法定义为无参方法(即省去空括号),对于有副作用的方法,不应该省去空括号,因为省略括号后这个方法调用就像是字段选中,因此使用方可能会对副作用感到意外。同理,每当你调用某个有副作用的函数,请确保在写调用代码时加上空括号,如果仅仅是访问某个属性,则可以省去括号
4 扩展类
class ArrayElement(conts: Array[String]) extends Element {
  def contents: Array[String] = conts
}
  • scala 继承也使用extends关键字,如果去掉extends子句,scala编译器会假定你的类扩展自 scala.AnyRef,这对应于java 平台 java.lang.Object

  • 继承的意思是超类的所有成员也是子类的成员,但是有两个例外,一是超类的私有成员并不会被子类继承,二是如果子类里已经实现了相同名称和参数的成员,那么该成员不会被继承

5 重写方法和字段
class ArrayElement(conts: Array[String]) extends Element {
  //contents为字段,字段重写方法  
  val contents: Array[String] = conts;
}
  • 统一访问原则只是 scala 比 java 在处理字段和方法上更加统一的一个方面,另一个区别是scala的字段和方法属于同一个命名空间,这使得用字段重写无参方法变为可能。
  • scala 禁止在同一个类中使用相同名称的字段和方法,这在 java 中是被允许的
  • 一般来说,scala 中只有两个命名空间,分别是:值(字段、方法、包和单例对象),类型(类和特质名) ,scala将字段和方法放在同一个命名空间的原因是为了让你可以用val 来重写一个无参方法,另一个原因是让你能引入包(而不仅仅是类型的名称)即单例对象的字段和方法。这在 java 中是不允许的
6 定义参数化字段
//类参数前加了val
class ArrayElement(val contents: Array[String]) extends Element {

}

//等价于
class ArrayElement(x123: Array[String]) extends Element {
  def contents: Array[String] = x123
}
  • 类参数前加上 val 或 var,这是同时定义参数和同名字段的简写方式,还可以在参数化字段添加修饰符,比如private、protected或者overried
7 调用超类构造方法
class LineElement(s: String) extends ArrayElement(Array(s)) {

}
8 使用 override 修饰符
class LineElement(s: String) extends ArrayElement(Array(s)) {
  override def width: Int = s.length
  //必须添加override修饰符	
  override def height: Int = 1
}
  • scala 强制要求所有重写父类具体成员时加上 override 修饰符,如果某个成员并不重写或继承基类中的某个成员,这个修饰符是禁用的
  • 脆弱基类问题是如果你在某个类的继承关系中对基类(通常叫作超类)添加新的成员,你将面临破坏使用方代码的风险,scala 并不能完全解决脆弱基类问题,而是得到一个编译器错误,这通常是更优的选择
9 多态和动态绑定

类型为基类的变量可以指向一个类型为子类的对象,这个现象的名称叫作多态。动态绑定是指被调用的方法实现是在运行时基于对象的类型来决定的,而不是变量或表达式的类型决定的

10 声明 final 成员

如果想确保某个成员不能被子类重写,可以在成员加上 final 修饰符,想确保某个类没有子类,可以在类声明之前加上 final 修饰符

11 使用组合与继承

如果追求代码复用,一般来说你应当优先选择组合而不是继承,只有继承才会受到基类脆弱问题的困扰,会在修改超类时不小心破坏了子类的代码,关于继承关系,那就是要建模的这个关系是否是is-a(是一个)的关系

12 实现above、beside和 toString
def above(that: Element): Element = {
    //++ 操作符将两个数组拼接在一起
    new ArrayElement(this.contents ++ that.contents)
}

//指令式风格,明显标志是使用下标遍历数组时使用的循环
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)
}

//函数式编程
def beside(that: Element): Element = {
    new ArrayElement(
        for (
            (line1, line2) <- that.contents zip this.contents
        ) yield line1 + line2
    )
}
  • 数组中 ++ 方法将两个数组拼接在一起,scala 中的数组是用 java 的数组表示的,只是支持更多的方法
  • 数组 zip 方法将两个数组转换成对偶(即Tuple2)的数组,例如 Array(1,2,3) zip Array(“a”, “b”) 将被求值为Array( (1, “a”) , (2, “b”) ) ,如果其中一个操作元组比另一个长,zip 将会扔掉多余的元素
13 定义工厂对象

使用工厂方法创建对象的好处是对象创建的逻辑被集中起来,而对象时如何用具体的类表示的可以被隐藏起来,这样既可以让你的类库更容易被理解,因为暴露的细节更少,同时还提供了更多的机会让你在未来在不破坏使用方代码的前提下改变类库的实现

14 增高和增宽
15 放在一起
16 结语
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值