这是对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让它变得更加简单。
假设你有Horse
和Dog
。它们都是有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()
|
但是这种写法违背了“组合胜于继承”原则。Horse
和Dog
不仅仅是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)
|
现在这种写法要好一些,因为它的扩展性更好,假设未来我们同时有FourLegged
和TwoLegged
,我们可以很方便地替换它们。
但是我仍然不喜欢这种写法,因为我不得不继承自类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并没有本质上的改动。
最后更新时间:
本文系作者原创,如转载请注明出处。欢迎留言讨论,或通过邮件进行沟通~