3.3.4 类与接口

原文链接: https://kotlinlang.org/docs/kotlin-tour-intermediate-classes-interfaces.html

3.3.4 类与接口

在入门导览中,你学会了如何使用类和数据类来存储数据,并维护一组可以在代码中共享的特征。最终,你会希望建立一个层级结构,从而在项目中高效地共享代码。本章讲解 Kotlin 提供的代码共享方式,以及它们如何让代码更安全、更易维护。

类继承

在前面的章节中,我们介绍了如何用扩展函数在不修改原始源码的情况下扩展类。但如果你面对的是比较复杂的情况,需要在多个类之间共享代码呢?这种情况下,你可以使用类继承。

默认情况下,Kotlin 中的类不能被继承。Kotlin 这样设计是为了防止意外的继承,并让你的类更易维护。

Kotlin 的类只支持单继承,也就是说一次只能从一个类继承。这个类称为父类。

一个类的父类又可以继承另一个类(祖父类),从而形成层级结构。Kotlin 类层级的最顶端是公共父类:Any。所有类最终都继承自 Any 类:

以 Any 类型为例的类层级示意图

Any 类自动提供了一个成员函数 toString()。因此,你可以在自己的任何类中使用这个继承来的函数。例如:

1
2
3
4
5
6
7
8
9
class Car(val make: String, val model: String, val numberOfDoors: Int)

fun main() {
    val car1 = Car("Toyota", "Corolla", 4)

    // Uses the .toString() function via string templates to print class properties
    println("Car1: make=${car1.make}, model=${car1.model}, numberOfDoors=${car1.numberOfDoors}")
    // Car1: make=Toyota, model=Corolla, numberOfDoors=4
}

如果你想用继承在多个类之间共享一些代码,请先考虑使用抽象类。

抽象类

抽象类默认就可以被继承。抽象类的用途是提供供其他类继承或实现的成员。因此,抽象类有构造器,但你不能用它创建实例。在子类中,你用 override 关键字定义父类属性和函数的具体行为。这样,你可以说子类「重写」了父类的成员。

提示: 当你定义继承来的函数或属性的行为时,我们称之为一次实现。

抽象类既可以包含带实现的函数和属性,也可以包含不带实现的函数和属性,后者称为抽象函数和抽象属性。

要创建抽象类,请使用 abstract 关键字:

1
abstract class Animal

要声明不带实现的函数或属性,同样使用 abstract 关键字:

1
2
abstract fun makeSound()
abstract val sound: String

例如,假设你想创建一个名为 Product 的抽象类,并从中派生出子类来定义不同的商品类别:

1
2
3
4
5
6
7
8
9
abstract class Product(val name: String, var price: Double) {
    // Abstract property for the product category
    abstract val category: String

    // A function that can be shared by all products
    fun productInfo(): String {
        return "Product: $name, Category: $category, Price: $price"
    }
}

在这个抽象类中:

  • 构造器有两个参数,分别表示商品的 name 和 price。
  • 有一个抽象属性,用字符串表示商品类别。
  • 有一个函数,用于打印商品信息。

接下来为电子产品创建一个子类。在子类中为 category 属性定义实现之前,你必须使用 override 关键字:

1
2
3
class Electronic(name: String, price: Double, val warranty: Int) : Product(name, price) {
    override val category = "Electronic"
}

Electronic 类:

  • 继承自 Product 抽象类。
  • 在构造器中多了一个参数:warranty,这是电子产品特有的。
  • 重写了 category 属性,使其包含字符串 "Electronic"。

现在,你可以这样使用这些类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
abstract class Product(val name: String, var price: Double) {
    // Abstract property for the product category
    abstract val category: String

    // A function that can be shared by all products
    fun productInfo(): String {
        return "Product: $name, Category: $category, Price: $price"
    }
}

class Electronic(name: String, price: Double, val warranty: Int) : Product(name, price) {
    override val category = "Electronic"
}

fun main() {
    // Creates an instance of the Electronic class
    val laptop = Electronic(name = "Laptop", price = 1000.0, warranty = 2)

    println(laptop.productInfo())
    // Product: Laptop, Category: Electronic, Price: 1000.0
}

虽然抽象类非常适合同以这种方式共享代码,但它们有局限:Kotlin 的类只支持单继承。如果你需要从多个来源继承,请考虑使用接口。

接口

接口与类相似,但有一些区别:

  • 你不能创建接口的实例。接口没有构造器,也没有类头。
  • 它们的函数和属性默认隐式可继承。在 Kotlin 中,我们说它们是「开放」的。
  • 如果你不给接口中的函数提供实现,也不需要把它们标记为 abstract。

与抽象类类似,你可以用接口定义一组函数和属性,供各个类稍后继承并实现。这种方式帮助你专注于接口所描述的抽象,而不是具体的实现细节。使用接口能让你的代码:

  • 更具模块化,因为它把不同部分隔离开来,使它们可以各自独立演进。
  • 更易理解,因为相关的函数被归入一个内聚的集合。
  • 更易测试,因为你可以快速地把某个实现换成用于测试的模拟实现。

要声明接口,请使用 interface 关键字:

1
interface PaymentMethod

接口实现

接口支持多继承,因此一个类可以同时实现多个接口。我们先看一个类实现一个接口的场景。

要创建一个实现接口的类,请在类头之后加一个冒号,后面跟上你想实现的接口名。接口名之后不用写圆括号 (),因为接口没有构造器:

1
class CreditCardPayment : PaymentMethod

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
interface PaymentMethod {
    // Functions are inheritable by default
    fun initiatePayment(amount: Double): String
}

class CreditCardPayment(val cardNumber: String, val cardHolderName: String, val expiryDate: String) : PaymentMethod {
    override fun initiatePayment(amount: Double): String {
        // Simulate processing payment with credit card
        return "Payment of $$amount initiated using Credit Card ending in ${cardNumber.takeLast(4)}."
    }
}

fun main() {
    val paymentMethod = CreditCardPayment("1234 5678 9012 3456", "John Doe", "12/25")
    println(paymentMethod.initiatePayment(100.0))
    // Payment of $100.0 initiated using Credit Card ending in 3456.
}

在这个示例中:

  • PaymentMethod 是一个接口,其中有一个没有实现的 initiatePayment() 函数。
  • CreditCardPayment 是一个实现了 PaymentMethod 接口的类。
  • CreditCardPayment 类重写了继承来的 initiatePayment() 函数。
  • paymentMethod 是 CreditCardPayment 类的一个实例。
  • 在 paymentMethod 实例上调用重写后的 initiatePayment() 函数,参数为 100.0。

要创建一个实现多个接口的类,请在类头之后加一个冒号,后面跟上你想实现的接口名,并用逗号分隔:

1
class CreditCardPayment : PaymentMethod, PaymentType

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
interface PaymentMethod {
    fun initiatePayment(amount: Double): String
}

interface PaymentType {
    val paymentType: String
}

class CreditCardPayment(val cardNumber: String, val cardHolderName: String, val expiryDate: String) : PaymentMethod,
    PaymentType {
    override fun initiatePayment(amount: Double): String {
        // Simulate processing payment with credit card
        return "Payment of $$amount initiated using Credit Card ending in ${cardNumber.takeLast(4)}."
    }

    override val paymentType: String = "Credit Card"
}

fun main() {
    val paymentMethod = CreditCardPayment("1234 5678 9012 3456", "John Doe", "12/25")
    println(paymentMethod.initiatePayment(100.0))
    // Payment of $100.0 initiated using Credit Card ending in 3456.

    println("Payment is by ${paymentMethod.paymentType}")
    // Payment is by Credit Card
}

在这个示例中:

  • PaymentMethod 是一个接口,其中有一个没有实现的 initiatePayment() 函数。
  • PaymentType 是一个接口,其中有一个未初始化的 paymentType 属性。
  • CreditCardPayment 是一个实现了 PaymentMethod 和 PaymentType 接口的类。
  • CreditCardPayment 类重写了继承来的 initiatePayment() 函数和 paymentType 属性。
  • paymentMethod 是 CreditCardPayment 类的一个实例。
  • 在 paymentMethod 实例上调用重写后的 initiatePayment() 函数,参数为 100.0。
  • 在 paymentMethod 实例上访问重写后的 paymentType 属性。

关于接口与接口继承的更多信息,请参阅接口。

委托

接口很有用,但如果你的接口包含很多函数,它的子类最终可能会带有大量样板代码。如果你只想重写类的某一小部分行为,就不得不大量重复自己。

提示: 样板代码是指在软件项目的多个位置被重复使用、几乎没有改动的代码块。

例如,假设你有一个名为 DrawingTool 的接口,其中包含若干函数和一个名为 color 的属性:

1
2
3
4
5
6
interface DrawingTool {
    val color: String
    fun draw(shape: String)
    fun erase(area: String)
    fun getToolInfo(): String
}

你创建了一个名为 PenTool 的类来实现 DrawingTool 接口,并为它的所有成员提供了实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class PenTool : DrawingTool {
    override val color: String = "black"

    override fun draw(shape: String) {
        println("Drawing $shape using a pen in $color")
    }

    override fun erase(area: String) {
        println("Erasing $area with pen tool")
    }

    override fun getToolInfo(): String {
        return "PenTool(color=$color)"
    }
}

你想创建一个行为与 PenTool 相同、但 color 值不同的类。一种做法是创建一个新类,让它接收一个实现了 DrawingTool 接口的对象(例如 PenTool 类的实例)作为参数,然后在类内部重写 color 属性。

但在这种情况下,你需要为 DrawingTool 接口的每个成员都添加实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
interface DrawingTool {
    val color: String
    fun draw(shape: String)
    fun erase(area: String)
    fun getToolInfo(): String
}

class PenTool : DrawingTool {
    override val color: String = "black"

    override fun draw(shape: String) {
        println("Drawing $shape using a pen in $color")
    }

    override fun erase(area: String) {
        println("Erasing $area with pen tool")
    }

    override fun getToolInfo(): String {
        return "PenTool(color=$color)"
    }
}
class CanvasSession(val tool: DrawingTool) : DrawingTool {
    override val color: String = "blue"

    override fun draw(shape: String) {
        tool.draw(shape)
    }

    override fun erase(area: String) {
        tool.erase(area)
    }

    override fun getToolInfo(): String {
        return tool.getToolInfo()
    }
}
fun main() {
    val pen = PenTool()
    val session = CanvasSession(pen)

    println("Pen color: ${pen.color}")
    // Pen color: black

    println("Session color: ${session.color}")
    // Session color: blue

    session.draw("circle")
    // Drawing circle with pen in black

    session.erase("top-left corner")
    // Erasing top-left corner with pen tool

    println(session.getToolInfo())
    // PenTool(color=black)
}

你可以看出,如果 DrawingTool 接口中的成员函数很多,CanvasSession 类中的样板代码就会非常庞大。不过,还有另一种办法。

在 Kotlin 中,你可以用 by 关键字把接口的实现委托给某个类实例。例如:

1
class CanvasSession(val tool: DrawingTool) : DrawingTool by tool

这里的 tool 是 PenTool 类实例的名称,成员函数的实现就委托给它。

现在你不必再在 CanvasSession 类中为成员函数添加实现。编译器会自动从 PenTool 类中获取这些实现。这样你就不必编写大量样板代码,而只需为你想要在子类中修改的行为编写代码。

例如,如果你想修改 color 属性的值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
interface DrawingTool {
    val color: String
    fun draw(shape: String)
    fun erase(area: String)
    fun getToolInfo(): String
}

class PenTool : DrawingTool {
    override val color: String = "black"

    override fun draw(shape: String) {
        println("Drawing $shape using a pen in $color")
    }

    override fun erase(area: String) {
        println("Erasing $area with pen tool")
    }

    override fun getToolInfo(): String {
        return "PenTool(color=$color)"
    }
}

class CanvasSession(val tool: DrawingTool) : DrawingTool by tool {
    // No boilerplate code!
    override val color: String = "blue"
}
fun main() {
    val pen = PenTool()
    val session = CanvasSession(pen)

    println("Pen color: ${pen.color}")
    // Pen color: black

    println("Session color: ${session.color}")
    // Session color: blue

    session.draw("circle")
    // Drawing circle with pen in black

    session.erase("top-left corner")
    // Erasing top-left corner with pen tool

    println(session.getToolInfo())
    // PenTool(color=black)
}

如果你愿意,也可以在 CanvasSession 类中重写某个继承来的成员函数的行为,但现在你不必为每一个继承来的成员函数都添加新的代码行。

更多信息请参阅委托。

练习

习题 1

假设你在开发一套智能家居系统。智能家居通常有不同类型的设备,它们都有一些基本功能,同时也有各自独特的行为。在下面的代码示例中,请补全名为 SmartDevice 的 abstract 类,使子类 SmartLight 能够成功编译。

然后,再创建另一个名为 SmartThermostat 的子类,它继承自 SmartDevice 类,并实现 turnOn() 和 turnOff() 函数,返回描述哪个温控器正在加热或已关闭的打印语句。最后,再添加一个名为 adjustTemperature() 的函数,它接收一个温度值作为输入,并打印:$name thermostat set to $temperature°C.。

提示

在 SmartDevice 类中添加 turnOn() 和 turnOff() 函数,这样你稍后就能在 SmartThermostat 类中重写它们的行为。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
abstract class // Write your code here

class SmartLight(name: String) : SmartDevice(name) {
    override fun turnOn() {
        println("$name is now ON.")
    }

    override fun turnOff() {
        println("$name is now OFF.")
    }

   fun adjustBrightness(level: Int) {
        println("Adjusting $name brightness to $level%.")
    }
}

class SmartThermostat // Write your code here

fun main() {
    val livingRoomLight = SmartLight("Living Room Light")
    val bedroomThermostat = SmartThermostat("Bedroom Thermostat")

    livingRoomLight.turnOn()
    // Living Room Light is now ON.
    livingRoomLight.adjustBrightness(10)
    // Adjusting Living Room Light brightness to 10%.
    livingRoomLight.turnOff()
    // Living Room Light is now OFF.

    bedroomThermostat.turnOn()
    // Bedroom Thermostat thermostat is now heating.
    bedroomThermostat.adjustTemperature(5)
    // Bedroom Thermostat thermostat set to 5°C.
    bedroomThermostat.turnOff()
    // Bedroom Thermostat thermostat is now off.
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
abstract class SmartDevice(val name: String) {
    abstract fun turnOn()
    abstract fun turnOff()
}

class SmartLight(name: String) : SmartDevice(name) {
    override fun turnOn() {
        println("$name is now ON.")
    }

    override fun turnOff() {
        println("$name is now OFF.")
    }

   fun adjustBrightness(level: Int) {
        println("Adjusting $name brightness to $level%.")
    }
}

class SmartThermostat(name: String) : SmartDevice(name) {
    override fun turnOn() {
        println("$name thermostat is now heating.")
    }

    override fun turnOff() {
        println("$name thermostat is now off.")
    }

   fun adjustTemperature(temperature: Int) {
        println("$name thermostat set to $temperature°C.")
    }
}

fun main() {
    val livingRoomLight = SmartLight("Living Room Light")
    val bedroomThermostat = SmartThermostat("Bedroom Thermostat")

    livingRoomLight.turnOn()
    // Living Room Light is now ON.
    livingRoomLight.adjustBrightness(10)
    // Adjusting Living Room Light brightness to 10%.
    livingRoomLight.turnOff()
    // Living Room Light is now OFF.

    bedroomThermostat.turnOn()
    // Bedroom Thermostat thermostat is now heating.
    bedroomThermostat.adjustTemperature(5)
    // Bedroom Thermostat thermostat set to 5°C.
    bedroomThermostat.turnOff()
    // Bedroom Thermostat thermostat is now off.
}

示例解答

习题 2

创建一个名为 Media 的接口,用来实现 Audio、Video、Podcast 之类的具体媒体类。你的接口必须包含:

  • 一个名为 title 的属性,表示媒体的标题。
  • 一个名为 play() 的函数,用于播放媒体。

然后,创建一个名为 Audio 的类来实现 Media 接口。Audio 类必须在构造器中使用 title 属性,并且还要有一个额外的、类型为 String 的 composer 属性。在类中实现 play() 函数,打印:"Playing audio: $title, composed by $composer"。

提示

你可以在类头中使用 override 关键字,在构造器中实现接口的属性。

1
2
3
4
5
6
7
8
9
interface // Write your code here

class // Write your code here

fun main() {
    val audio = Audio("Symphony No. 5", "Beethoven")
    audio.play()
   // Playing audio: Symphony No. 5, composed by Beethoven
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
interface Media {
    val title: String
    fun play()
}

class Audio(override val title: String, val composer: String) : Media {
    override fun play() {
        println("Playing audio: $title, composed by $composer")
    }
}

fun main() {
    val audio = Audio("Symphony No. 5", "Beethoven")
    audio.play()
   // Playing audio: Symphony No. 5, composed by Beethoven
}

示例解答

习题 3

你正在为一个电商应用构建支付处理系统。每种支付方式都需要能够授权支付并处理交易,有些支付还需要能够处理退款。

  1. 在 Refundable 接口中,添加一个名为 refund() 的函数来处理退款。

  2. 在 PaymentMethod 抽象类中:

  • 添加一个名为 authorize() 的函数,它接收一个金额并打印包含该金额的消息。
  • 添加一个同样接收金额的抽象函数 processPayment()。
  1. 创建一个名为 CreditCard 的类,实现 Refundable 接口和 PaymentMethod 抽象类。在这个类中为 refund() 和 processPayment() 函数添加实现,使它们打印以下语句:
  • "Refunding $amount to the credit card."
  • "Processing credit card payment of $amount."
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
interface Refundable {
    // Write your code here
}

abstract class PaymentMethod(val name: String) {
    // Write your code here
}

class CreditCard // Write your code here

fun main() {
    val visa = CreditCard("Visa")

    visa.authorize(100.0)
    // Authorizing payment of $100.0.
    visa.processPayment(100.0)
    // Processing credit card payment of $100.0.
    visa.refund(50.0)
    // Refunding $50.0 to the credit card.
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
interface Refundable {
    fun refund(amount: Double)
}

abstract class PaymentMethod(val name: String) {
    fun authorize(amount: Double) {
        println("Authorizing payment of $$amount.")
    }

    abstract fun processPayment(amount: Double)
}

class CreditCard(name: String) : PaymentMethod(name), Refundable {
    override fun processPayment(amount: Double) {
        println("Processing credit card payment of $$amount.")
    }

    override fun refund(amount: Double) {
        println("Refunding $$amount to the credit card.")
    }
}

fun main() {
    val visa = CreditCard("Visa")

    visa.authorize(100.0)
    // Authorizing payment of $100.0.
    visa.processPayment(100.0)
    // Processing credit card payment of $100.0.
    visa.refund(50.0)
    // Refunding $50.0 to the credit card.
}

示例解答

习题 4

你有一个具备一些基本功能的简单消息应用,但你希望为智能消息添加一些功能,同时不要大量重复代码。

在下面的代码中,定义一个名为 SmartMessenger 的类,它继承 Messenger 接口,但把实现委托给 BasicMessenger 类的一个实例。

在 SmartMessenger 类中,重写 sendMessage() 函数以发送智能消息。该函数必须接收一个 message 作为输入,并返回一条打印语句:"Sending a smart message: $message"。此外,还要调用 BasicMessenger 类中的 sendMessage() 函数,并在消息前加上 [smart] 前缀。

注意: 你不需要在 SmartMessenger 类中重写 receiveMessage() 函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
interface Messenger {
    fun sendMessage(message: String)
    fun receiveMessage(): String
}

class BasicMessenger : Messenger {
    override fun sendMessage(message: String) {
        println("Sending message: $message")
    }

    override fun receiveMessage(): String {
        return "You've got a new message!"
    }
}

class SmartMessenger // Write your code here

fun main() {
    val basicMessenger = BasicMessenger()
    val smartMessenger = SmartMessenger(basicMessenger)

    basicMessenger.sendMessage("Hello!")
    // Sending message: Hello!
    println(smartMessenger.receiveMessage())
    // You've got a new message!
    smartMessenger.sendMessage("Hello from SmartMessenger!")
    // Sending a smart message: Hello from SmartMessenger!
    // Sending message: [smart] Hello from SmartMessenger!
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
interface Messenger {
    fun sendMessage(message: String)
    fun receiveMessage(): String
}

class BasicMessenger : Messenger {
    override fun sendMessage(message: String) {
        println("Sending message: $message")
    }

    override fun receiveMessage(): String {
        return "You've got a new message!"
    }
}

class SmartMessenger(val basicMessenger: BasicMessenger) : Messenger by basicMessenger {
    override fun sendMessage(message: String) {
        println("Sending a smart message: $message")
        basicMessenger.sendMessage("[smart] $message")
    }
}

fun main() {
    val basicMessenger = BasicMessenger()
    val smartMessenger = SmartMessenger(basicMessenger)

    basicMessenger.sendMessage("Hello!")
    // Sending message: Hello!
    println(smartMessenger.receiveMessage())
    // You've got a new message!
    smartMessenger.sendMessage("Hello from SmartMessenger!")
    // Sending a smart message: Hello from SmartMessenger!
    // Sending message: [smart] Hello from SmartMessenger!
}

示例解答

另请参阅

上一步

下一步