这是对Medium上Kotlin made Interface so much better一文的翻译,内容偏基础。

在Java中,“接口”一开始是作为一项新的编程特性被提出的。它描述了一种“可以是”关系,而非“一定是”。这也使得它可以用于多重继承(例如,某物可以是许多特性的集合,但只能是某个事物。译者注:猫可以同时具有喵喵叫和会爬树两种属性,但猫只能是猫,不可能是狗)。

然而,正如我们所见到的,直到Java7(Java7是Android工程师主要的开发语言。译者注:原文写于2018年10月),接口仍然有很多缺点,这些缺点使得接口变得很难用,以至于一些人宁愿用回抽象类。

接着我们迎来了Kotlin,我将在本文为大家展示Kotlin在继承关系中的强大能力。

Kotlin扩展接口能力

在Java中

在Java7中,接口自己不能包含方法实现,因此对于接口实现类而言,必须实现接口中的所有方法。

这是一个麻烦,它削弱了接口的可扩展能力。

假设我们有如下的Movable接口。

1
2
3
4
5
6
7
8
9
10
interface Movable {
int legsCount();
}

class Horse implements Movable {
@Override
public int legsCount() {
return 4;
}
}

突然我们发现除了统计腿的数量,还需要统计翅膀的数量。所以我们增加了wingsCount()方法。

对于所有实现了Movable接口的类,这是一个坏消息,因为它们必须修改代码以适配接口变更。例如,Horse必须修改如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Movable {
int legsCount();
int wingsCount();
}
class Horse implements Movable {
@Override
public int legsCount() {
return 4;
}
@Override
public int wingsCount() {
return 0;
}
}

在Kotlin中

一开始我们有

1
2
3
4
5
6
7
interface Movable {
fun legsCount() : Int
}

class Horse : Movable {
override fun legsCount() = 4
}

随后我们可以轻松地扩展它

1
2
3
4
5
6
7
8
interface Movable {
fun legsCount() : Int
fun wingsCount() : Int { return 0 } // 注意这里可以为方法提供默认实现
}

class Horse : Movable {
override fun legsCount() = 4 // 实现类不需进行任何变动
}

甚至可以做的更加复杂,而Horse类不需要因此进行任何改动!

1
2
3
4
5
6
7
8
9
interface Movable {
fun legsCount(): Int { return 0 }
fun wingsCount(): Int { return 0 }
fun canFly(): Boolean { return wingsCount() > 1 }
fun canWalk(): Boolean { return legsCount() > 1 }
}
class Horse : Movable {
override fun legsCount() = 4
}

Kotlin使接口真正地“可覆写”

剑桥大辞典里对于“override”的定义是

to decide against or refuse to accept a previous decision, an order, a person, etc.

在Java世界中,接口没有覆写任何东西。

但是在Kotlin世界里,见如下例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Movable {
fun legsCount() : Int { return 0 }
fun wingsCount() : Int { return 0 }
fun canFly() : Boolean { return wingsCount() > 1 }
fun canWalk() : Boolean { return legsCount() > 1 }
}

class Horse : Movable {
var isSick = false
override fun legsCount() = 4
override fun canWalk() : Boolean {
if (isSick) {
return false
}
return super.canWalk()
}
}

如果我们让isSick = true,那么无论这个生物有多少条腿,canWalk()方法都会返回false。嗯,真正意义上的“覆写”。

Kotlin使接口更像一个对象

在Java世界(我认为包括Java8和9),接口并不允许包含常量之外的任何属性。

充其量我们可以声明一个变量的存取方法,例如legsCount()

在Kotlin中

Kotlin允许接口中包含属性。

比起这种写法

1
2
3
4
5
6
7
interface Movable {
fun legsCount(): Int
fun canWalk() = legsCount() > 1
}
class Horse : Movable {
override fun legsCount() = 4
}

你可以将其简化成

1
2
3
4
5
6
7
interface Movable {
val legsCount : Int
fun canWalk(): Boolean = legsCount > 1
}
class Horse : Movable {
override val legsCount = 4
}

在接口中使用属性有一些局限,它不可以有backfield属性,意味着它不可变。所以它仍然是无状态的。

此外,不允许在接口中为属性赋初值。

Kotlin使接口更好地为组合服务

你也许听说过“组合胜过继承”原则。Kotlin让它变得更加简单。

假设你有HorseDog。它们都是有4条腿的动物。

一种写法如下

1
2
3
4
5
6
7
8
9
10
interface Movable {
val legsCount : Int
fun canWalk() = legsCount > 1
}
class Horse : Movable {
override val legsCount = 4
}
class Dog : Movable {
override val legsCount = 4
}

这种写法太繁琐,具体表现在如下地方

  • 在每一个实现类里都重复override val legsCount = 4的代码段
  • 如果我们有更多要覆写的方法,或者更多4条腿的类,我们必须重复做一样的事
  • 未来某一天,如果我们必须把4改成four,或者增加更多功能……

这将是一个灾难。太难扩展了。

也许我们可以利用类的继承关系?

1
2
3
4
5
6
7
8
9
10
11
interface Movable {
val legsCount: Int
fun canWalk() = legsCount > 1
}

open class FourLegged : Movable {
override val legsCount = 4
}

class Horse : FourLegged()
class Dog : FourLegged()

但是这种写法违背了“组合胜于继承”原则。HorseDog不仅仅是FourLegged,也可以是其他什么东西,这种写法让他们不可扩展成为其他类型(例如:Pet)。

这是不可扩展的☹️

让我们用组合代替继承(传统写法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Movable {
val legsCount: Int
fun canWalk() = legsCount > 1
}
object FourLegged : Movable {
override val legsCount = 4
}
class Horse : Movable {
private val movable = FourLegged
override val legsCount
get() = movable.legsCount
}
class Dog : Movable {
private val movable = FourLegged
override val legsCount
get() = movable.legsCount
}

不知道你怎么想,我并不喜欢这种写法,所以让我们稍事改动…

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Movable {
val legsCount: Int
fun canWalk() = legsCount > 1
}
object FourLegged : Movable {
override val legsCount = 4
}
open class MovableImpl(private val movable: Movable) : Movable {
override val legsCount
get() = movable.legsCount
}
class Horse : MovableImpl(FourLegged)
class Dog : MovableImpl(FourLegged)

现在这种写法要好一些,因为它的扩展性更好,假设未来我们同时有FourLeggedTwoLegged,我们可以很方便地替换它们。

但是我仍然不喜欢这种写法,因为我不得不继承自类MovableImpl。所幸我们有Kotlin,我们看下Kotlin如何处理此类问题…

Kotlin的方式:使用By代理简化组合写法

在Kotlin的接口写法中,我们可以使用By关键字轻松实现代理模式。来看看

1
2
3
4
5
6
7
8
9
10
11
interface Movable {
val legsCount: Int
fun canWalk() = legsCount > 1
}

object FourLegged : Movable {
override val legsCount = 4
}

class Horse : Movable by FourLegged
class Dog : Movable by FourLegged

太棒了!🤩。相信你也看得出这种写法的好处。

如果你想对“组合胜于继承”有更深入的了解,可以阅读以下这篇文章:

Composition over inheritance in Kotlin way

Kotlin使接口更好地实现多重继承

在Java7中使用多重继承是一件痛苦的事,因为我们不得不实现所有的接口,并覆写其中全部方法。

译者注:原文这里用的是inherit from all interfaces,可见原作者对“继承”和“实现”并不是严格区分。实际上在Java中不可以“多重继承”(对抽象类而言),而应当是“多重实现”(对接口而言)。

即使这样做,也无法从父类中继承任何属性。(当然了,Java的接口里是不允许有非常量属性的)

在Kotlin中

借助于By代理,我们来看看下面这个例子

假设我们的动物接口有两个属性

1
2
3
4
5
6
7
8
9
interface Movable {
val legsCount: Int
fun canWalk() = legsCount > 1
}

interface Pet {
val name: String
fun liveInhouse() = false
}

让我们提供一些更具体的内容(也就是接口实现),以便于我们可以使新创建的类使用这些具体内容(代理)。

1
2
3
4
5
6
7
object FourLegged : Movable {
override val legsCount = 4
}

class InHousePet(override val name: String) : Pet {
override fun liveInHouse() = true
}

然后我们可以创建Cat类了,它是一个FourLegged动物,并且也是InHousePet。所以为了同时继承这两点,我们采用如下写法

1
2
class CatBy(name: String) :
Pet by InHousePet(name), Movable by FourLegged

只要你愿意,可以增加更多的属性

1
2
3
4
classCatBy(name: String, trainer: Trainer):
Pet by InHousePet(name),
Movable by FourLegged,
Trainable by Professional(trainer)

或者,你也可以覆写其中的值,只要你愿意!

1
2
3
4
5
6
7
8
9
10
classCatBy(name: String, trainer: Trainer):
Pet by InHousePet(name),
Movable by FourLegged,
Trainable by Professional(trainer) {

override val legsCount: Int
get() = 100

override fun liveInHouse() = true
}

感觉自己像是在写C++🤪


译后感

在Kotlin的接口中可以声明只读属性,其实就相当于Java的getXXX方法,只不过通过Kotlin一贯的get/set省略写法进行了简化。

by关键字用于代理,是Kotlin里面中阶的知识,我们可以为一个接口声明一个代理对象,从而分散目标类的职责。

在其它方面,Kotlin中的接口写法相比于Java并没有本质上的改动。