Scala的组合(Composition)和继承(Inherit

2018-03-13  本文已影响0人  Grits

Key Words: Composition, inheritance, abstract classs, parameterless methods, extending classes, overriding methods and fields, parametric fields, invoking superclass constructors, polymorphism and dynamic binding, final members and classes, factory objects and methods.

组合和继承

组合意味着一个类持有另外一个类的引用,来帮助此类来完成任务
继承是父/子类的关系

组合和继承是根据已有的类来定义新类的两种方式。当你倾向于代码的重用,你应该选择组合而不是继承。因为继承会导致脆弱基类(fragile base class)问题,很容易破坏子类的实现

询问自己,继承的模型是否是is a关系,客户端是否想把子类当做超类来使用,如果是这种is a关系,则需要使用继承;如果是has a关系,则使用组合。从设计模式上讲,优先使用组合而非继承

抽象类(Abstract class)

使用abstract来修饰一个类,即可将此类申明为抽象类

abstract class Foo

需要注意的地方:

1.一个类含有抽象的成员,此类必须要声明为抽象
2.一个类声明为抽象,则此类可以含有抽象成员
3.在抽象类中,抽象成员不必使用abstract修饰符

也就是说,含有抽象成员是声明一个类为抽象类的充分条件

无参方法(Parameterless methods)

定义一个方法,该方法没有参数列表(paremeter list),该方法就是无参方法。如果一个方法含有空的圆括号,叫做空参方法(empty-param methods)

def width: Int // parameterless methods.
def width() : Int // empty-param methods.

不论是方法没有参数,还是方法访问可变状态(只读)时,都推荐使用无参方法。这个约定支持统一访问原则(uniform access principle)客户端代码不受你决定将一个属性实现为字段还是方法的影响

总结下来,在Scala中鼓励使用不带参数,当没有副作用的时候,推荐使用无参方法。反过来,你不应该定义有副作用的无参方法,那样会给你的调用方造成很多的困扰。如果你调用一个方法具有副作用,那么也应该带上空括号(Empty parentheses)。

另外一种思考的方式,如果函数执行操作(performs an operation),则使用圆括号。如果仅仅提供对属性的访问,则去掉圆括号。

扩展类(Extending class)

使用extends关键字来对类进行扩展,也就是所谓的继承

继承(Inheritance)意味着超类的所有成员也会成为子类的成员,但是有两点除外:

1.超类的私有成员无法继承到子类中
2.如果子类中有实现和超类一样签名的成员,那么超类的该成员也无法继承到子类中,这也就是所谓的重写(overrides)

如果超类中的成员是抽象的,在子类中是具体的(concrete),叫做子类实现了超类的抽象成员

重写方法和字段(Overriding methods and fields)

统一访问原则的一个方面,就是Scala对待字段和方法更加的统一。在Scala中,字段和方法属于同一个命名空间(就是说在Scala中,禁止在同一个类中出现同名的方法和字段),这就为字段重写为一个无参方法提供了可能

总的来说,Scala拥有两个命名空间(Java含有四个,字段、方法、类型、包)。

所以可以在Scala中,将一个无参方法重写为一个字段

abstract class Foo {
  def bar: Int
}

class FooX extends Foo {
  val bar = 1
}

参数化字段(Parametric fields)

使用参数化字段可以消灭代码的坏味道(code smell)
要区别于类参数,其对外不可见

关于参数化字段的用法:

1.可以使用var来进行修饰,表示该字段是可以重新赋值的(reassignable)
2.可以添加修饰符(modifiers),比如 private,protected,override 到参数化字段

使用参数化字段来重写无参方法时需要注意:该参数化字段不能使用var修饰,只能使用val,同样也不能改变原来方法的可见性(private, protected)

abstract class Animal {
  def sound: String
}

class Cat(val sound: String) extends Animal

class Dog(
        private val color: String,
        protected val weight: Int, 
        val sound: String
      ) extends Animal

class Garfield(
        var color: String, 
        override val sound: String
      ) extends Cat(sound)

val cat = new Garfield("Yellow", "Wow")

println(cat.sound)
println(cat.color)

按照我的理解,类参数,也是参数化字段的一种,只不过是在编译时前面默认添加了private修饰符而已

重写操作符(Override modifiers)

在 Scala 中,如果对父类的一个具体方法进行重写,则必须要使用override修饰符,如果是对父类的抽象方法进行重写,则override则是可选的,如果不是重写或者实现父类的成员,那么override修饰符是禁止使用的
关于重写的上述约定,在系统演进(evolution)过程中非常重要

当然override也会带来脆弱基类(fragile base class)的问题,就是在基类中添加已经在客户端子类中已有的方法,将会导致编译出错

多态和动态绑定(Polymorphism and Dynamic binding)

多态可以从技术上理解为动态绑定。也就是说实际上具体哪个方法实现的调用取决于运行时的对象,而不是变量类型或者表达式

最终成员(Final members)

如果想要在继承层次中,使得某个成员不能被子类重写,可以使用final修饰符
如果想要整个类都不被子类化,可以在类定义上添加final修饰符

工厂对象(Factory Object)

工厂对象就是包含那些构建其他对象的对象。客户端可以使用工厂方法来构建对象,而不是直接使用new关键字来进行构建

使用工厂对象来构建对象会有很多的好处:

1.将创建过程中心化且隐藏对象的构建细节
2.为将来提供更多的机会来修改实现代码而不至于破坏客户端代码

上一篇下一篇

猜你喜欢

热点阅读