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.为将来提供更多的机会来修改实现代码而不至于破坏客户端代码
网友评论