面向对象的编程

优质
小牛编辑
142浏览
2023-12-01

面向对象的编程

Scala的博大很大程度上在于它的对象系统。Scala中所有的值都是对象,就这一意义而言Scala是门纯粹的语言;基本类型和组合类型没有区别。Scala也提供了mixin的特性允许更多正交地、细粒度地构造一些在编译时受益于静态类型检测的可被灵活组装的模块。

mixin系统的背后动机之一是消除传统的依赖注入。这种“组件风格(component style)”编程的高潮是是the cake pattern.

依赖注入

在我们的使用中,我们发现Scala本身删除了很多经典(构造函数)依赖注入的语法开销,我们更愿意就这样用: 它更清晰,依赖仍然植根于(构造)类型,而类构造语法是如此微不足道而变得轻而易举。有些无聊,简单,但有效。对模块化编程时使用依赖注入,特别是,组合优于继承—这使得程序更加模块化和可测试的。当遇到需要继承的情况,问问自己:在语言缺乏对继承支持的情况下如何构造程序?答案可能是令人信服的。

依赖注入典型的使用到 trait (译注:可以理解为和Java中Interface相似)

  1. trait TweetStream {
  2. def subscribe(f: Tweet => Unit)
  3. }
  4. class HosebirdStream extends TweetStream ...
  5. class FileStream extends TweetStream ..
  6. class TweetCounter(stream: TweetStream) {
  7. stream.subscribe { tweet => count += 1 }
  8. }

这是常见的注入工厂 — 用于产生其他对象的对象。在这些例子中,更青睐用简单的函数而非专有的工厂类型。

  1. class FilteredTweetCounter(mkStream: Filter => TweetStream) {
  2. mkStream(PublicTweets).subscribe { tweet => publicCount += 1 }
  3. mkStream(DMs).subscribe { tweet => dmCount += 1 }
  4. }

Trait

依赖注入不妨碍使用公共接口,或在trait中实现公共代码。恰恰相反—正是因为以下原因而高度鼓励使用trait:一个具体的类可以实现多接口(traits),公共的代码可以通过这些类复用。

保持traits简短并且是正交的:不要把分离的功能混在一个trait里,考虑将最小的相关的意图放在一起。例如,想象一下你要做一些IO的操作:

  1. trait IOer {
  2. def write(bytes: Array[Byte])
  3. def read(n: Int): Array[Byte]
  4. }

分离两个行为:

  1. trait Reader {
  2. def read(n: Int): Array[Byte]
  3. }
  4. trait Writer {
  5. def write(bytes: Array[Byte])
  6. }

可以将它们以混入(mix)的方式实现一个IOer : new Reader with Writer…接口最小化促使更好的正交性和更清晰的模块化。

可见性

Scala有很丰富的可见性修饰。使用这些可见性修饰很重要,因为它们定义了哪些构成公开API。公开APIs应该限制,这样用户不会无意中依赖实现细节并限制了作者修改它们的能力:
它们对于好的模块化设计是至关重要的。一般来说,扩展公开APIs比收缩公开的APIs容易的多。差劲的注释(annotation)也能危害到你代码向后的二进制兼容性。(译注:comments和annotation都可翻译成注释,但意义不同。annotation在Java和Scala有特定的含义)

private[this]

一个类的成员标记为私有的,

  1. private val x: Int = ...

它对这个类的所有实例来说都是可见的(但对其子类不可见)。大多情况,你想要的是 private[this] 。

  1. private[this] val: Int = ..

这个修饰限制了它只对当前特定的实例可见。Scala编译器会把private[this]翻译为一个简单的字段访问(因为访问仅限于静态定义的类),这样有时有助于性能优化。

单例类型

在Scala中创建单例类型是很常见的,例如:

  1. def foo() = new Foo with Bar with Baz {
  2. ...
  3. }

在这种情况下,可以通过声明返回类型来限制可见性:

  1. def foo(): Foo with Bar = new Foo with Bar with Baz {
  2. ...
  3. }

foo()方法的调用者会看到以返回实例(Foo with Bar)的受限视图。

结构类型

不要在正常情况下使用结构类型。结构类型有着便利且强大的特性,但不幸的是在JVM上的实现不是很高效。
然而——由于实现的怪癖——它提对执行反射(reflection)供了很好的简写形式。

  1. val obj: AnyRef
  2. obj.asInstanceOf[{def close()}].close()