Scala 中方法扩展实践

时间:2022-09-21 07:31:18

前言

这个名字不知道取得是否合适,简单来说要干的事情就是给某个类型添加一些扩展方法,此场景在各种语言中都会用到,比如 C# 语言,如果我们使用一个别人写好的类库,而又想给某个类库添加一些自己封装的方法,最好的方式就是使用扩展方法,具体实现方式此处不赘述。

起初,我以为在 Scala 中也是这样使用的,但是直到今天我才恍然大悟,在 Scala 中扩展方法其实不是那么简单,此处说的不简单主要说的是实现的意义不简单,而不是实现方法。本文对此进行简单介绍。

一、 实现方法

首先,来说明实现方法,正如上文所说,在 Scala 中其实实现起来也很容易。

首先我们有一个要扩展的类型假定为 C,定义如下:

trait A {
def play = println("play")
}

就是这么简单的一个类,包含一个 play 方法,当然可以有各种各样的子类继承于他,及包含其他方法。

第二步,定义一个扩展方法的类型:

trait BB[+T] {
def self: T
}

此类用于包装我们的被扩展类型,其中 self 就是一个要扩展类型的实例。(此处名字取 BB 实非本意,不知是 scala bug 还是其他问题,如果此处只使用 B 来命名,下面会报错,有知情者烦请指点一二。)

第三步,定义一个 trait 继承自 BB:

trait C extends BB[A] {
def draw = self.play
}

此类型里可以定义一系列的方法,这些方法就是即将被扩展的方法,我们可以直接使用 self 来表示被扩展的对象实例,可以直接调用他的方法。

第四步,实现一个隐式类型,将 A 对象隐式转换为 C:

implicit class D(val self: A) extends C

最终,我们可以直接对 A 对象的实例调用扩展方法:

new A {}.draw

二、分析

思路缜密,逻辑清晰((*_*)),实现起来很容易。开始的时候我以为就是这样,所以为每个类型写了一堆的扩展方法,自我感觉良好,调用起来很方便。

但是这个地方有个问题,既然我们自己定义了类型A,为什么不直接将 draw 方法也写到这里面呢,瞎折腾啥,我之前是这么想的,也是这么用的。然而,今天我突然意识到一个问题,在我使用的一个类库中,很多自己定义的类型也采用此种方式定义了大量的扩展方法,那我就纳闷了,为什么不直接写到类型的定义中呢?

沉思半天,我恍然大悟,这是一种良好的封装方法。简单说来就是 A 这个类型可能包含了大量的不同种类的方法,比如对于地理信息系统来说,一个瓦片可以包含投影、裁剪、切割等多种种类的方法,每个种类可能包含了一系列方法,所以采用这种方式的好处就是可以将这些不同种类的方法放到不同的扩展类型中进行管理,实现良好的封装。并且在后续调用中,无需判断此对象是否是 A 对象,只要判断此对象是否是 C 对象即可直接调用 C 中的方法,这样就对功能实现了良好的划分。

三、结束

看似一个简单的扩展方法,也有如此多的深层次逻辑,还是需要学会思考、深入的思考,这样才能发现更多 coding 中美的地方。