在 Kotlin 中使用类和对象

1. 开始之前

本 Codelab 将教您如何在 Kotlin 中使用类和对象。

类提供了一个蓝图,可以根据它构建对象。对象是类的一个实例,包含特定于该对象的数据。您可以互换使用对象或类实例。

打个比方,想象您要建造一座房子。类类似于建筑师的设计图,也称为蓝图。蓝图不是房子;它是建造房子的说明。房子是根据蓝图建造的实际事物,即对象。

就像房屋蓝图指定了多个房间,每个房间都有自己的设计和用途一样,每个类也有自己的设计和用途。要了解如何设计类,您需要熟悉面向对象编程 (OOP),这是一个教您将数据、逻辑和行为封装到对象中的框架。

OOP 可帮助您将复杂的现实问题简化为更小的对象。OOP 有四个基本概念,您将在本 Codelab 的稍后部分详细了解每个概念。

  • 封装。 将相关属性和对其执行操作的方法封装在一个类中。例如,考虑您的手机。它封装了摄像头、显示屏、存储卡以及其他一些硬件和软件组件。您无需担心组件内部是如何连接的。
  • 抽象。 封装的延伸。其思想是尽可能隐藏内部实现逻辑。例如,要使用手机拍照,您只需打开相机应用,将手机对准想要拍摄的场景,然后点击按钮即可拍照。您无需知道相机应用是如何构建的,也无需知道手机上的相机硬件实际上是如何工作的。简而言之,相机应用的内部机制以及手机相机如何捕捉照片已被抽象化,以便您可以执行重要任务。
  • 继承。 通过建立父子关系,使您可以在其他类的特征和行为的基础上构建一个类。例如,有许多不同的制造商生产运行 Android 操作系统的各种移动设备,但每种设备的 UI 都不同。换句话说,制造商继承了 Android 操作系统功能,并在其之上构建了自定义内容。
  • 多态。 该词改编自希腊词根 poly-,意为“许多”,以及 -morphism,意为“形式”。多态性是能够以单一、通用方式使用不同对象的能力。例如,当您将蓝牙音箱连接到手机时,手机只需要知道有一个设备可以通过蓝牙播放音频。但是,您可以选择各种蓝牙音箱,您的手机无需知道如何与每个特定的音箱一起工作。

最后,您将了解属性委托,它们提供可重用的代码,以简洁的语法管理属性值。在本 Codelab 中,您将在构建智能家居应用的类结构时学习这些概念。

前提条件

  • 如何在Kotlin Playground中打开、编辑和运行代码。
  • 了解 Kotlin 编程基础,包括变量、函数以及 println()main() 函数。

您将学习的内容

  • OOP 概述。
  • 什么是类。
  • 如何使用构造函数、函数和属性定义类。
  • 如何实例化对象。
  • 什么是继承。
  • IS-A 和 HAS-A 关系之间的区别。
  • 如何重写属性和函数。
  • 什么是可见性修饰符。
  • 什么是委托以及如何使用 by 委托。

您将构建的内容

  • 一个智能家居类结构。
  • 表示智能设备的类,例如智能电视和智能灯。

您需要的内容

  • 一台具有互联网访问和网络浏览器的计算机。

2. 定义类

定义类时,您指定该类的所有对象应该拥有的属性和方法。

类定义以 class 关键字开头,后跟一个名称和一对大括号。开大括号之前的语法部分也称为类头。在大括号中,您可以为类指定属性和函数。您很快就会学习属性和函数。您可以在此图表中看到类定义的语法。

It starts with class keyword followed by name, set of opening and closing curly braces. The curly braces contain the body of class which describes its blue print.

以下是建议的类命名约定:

  • 您可以选择任何您想要的类名,但不要使用 Kotlin 关键字作为类名,例如 fun 关键字。
  • 类名采用 PascalCase 格式编写,因此每个单词的首字母都大写,且单词之间没有空格。例如,在 SmartDevice 中,每个单词的首字母大写,且单词之间没有空格。

一个类包含三个主要部分:

  • 属性。指定类对象属性的变量。
  • 方法。包含类行为和操作的函数。
  • 构造函数。一个特殊的成员函数,用于在定义它的程序中创建类的实例。

这不是您第一次使用类。在之前的 Codelab 中,您学习了数据类型,例如 IntFloatStringDouble 数据类型。这些数据类型在 Kotlin 中定义为类。当您定义变量(如此代码段所示)时,您会创建一个 Int 类的对象,并使用 1 值实例化该对象。

val number: Int = 1

定义一个 SmartDevice

  1. Kotlin Playground中,将内容替换为空的 main() 函数。
fun main() {
}
  1. main() 函数之前的行上,定义一个 SmartDevice 类,其主体包含一个 // empty body 注释。
class SmartDevice {
    // empty body
}

fun main() {
}

3. 创建类实例

正如您所学,类是对象的蓝图。Kotlin 运行时使用类(或蓝图)来创建该特定类型的对象。使用 SmartDevice 类,您拥有了智能设备的蓝图。要在程序中拥有一个实际的智能设备,您需要创建一个 SmartDevice 对象实例。实例化语法以类名开头,后跟一对括号,如此图所示。

1d25bc4f71c31fc9.png

要使用对象,您需要创建对象并将其赋值给变量,这类似于您定义变量的方式。您使用 val 关键字创建不可变变量,使用 var 关键字创建可变变量。valvar 关键字后跟变量名,然后是 = 赋值运算符,然后是类对象的实例化。您可以在此图表中看到语法。

f58430542f2081a9.png

SmartDevice 类实例化为对象

  • main() 函数中,使用 val 关键字创建一个名为 smartTvDevice 的变量,并将其初始化为 SmartDevice 类的一个实例。
fun main() {
    val smartTvDevice = SmartDevice()
}

4. 定义类方法

在单元 1 中,您了解到:

  • 函数的定义使用 fun 关键字,后跟一对括号和一对大括号。大括号包含代码,即执行任务所需的指令。
  • 调用函数会执行该函数中包含的代码。

类可以执行的操作在类中定义为函数。例如,假设您拥有一个智能设备,一个智能电视或智能灯,您可以用手机将其打开和关闭。智能设备在编程中转换为 SmartDevice 类,打开和关闭它的操作由 turnOn()turnOff() 函数表示,它们启用开和关的行为。

在类中定义函数的语法与您之前学到的语法相同。唯一的区别是函数放置在类主体中。当您在类主体中定义函数时,它被称为成员函数或方法,它代表类的行为。在本 Codelab 的其余部分,当函数出现在类主体中时,它们被称为方法。

SmartDevice 类中定义 turnOn()turnOff() 方法

  1. SmartDevice 类的主体中,定义一个空的 turnOn() 方法。
class SmartDevice {
    fun turnOn() {

    }
}
  1. turnOn() 方法的主体中,添加一个 println() 语句,然后向其传递一个 "Smart device is turned on." 字符串。
class SmartDevice {
    fun turnOn() {
        println("Smart device is turned on.")
    }
}
  1. turnOn() 方法之后,添加一个 turnOff() 方法,该方法打印一个 "Smart device is turned off." 字符串。
class SmartDevice {
    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}

对象上调用方法

到目前为止,您已经定义了一个作为智能设备蓝图的类,创建了该类的一个实例,并将该实例分配给了变量。现在您将使用 SmartDevice 类的方法来打开和关闭设备。

调用类中的方法与您在之前的 Codelab 中从 main() 函数调用其他函数的方式类似。例如,如果您需要从 turnOn() 方法调用 turnOff() 方法,您可以编写类似于此代码片段的代码:

class SmartDevice {
    fun turnOn() {
        // A valid use case to call the turnOff() method could be to turn off the TV when available power doesn't meet the requirement.
        turnOff()
        ...
    }

    ...
}

要在类外部调用类方法,首先是类对象,后跟 . 运算符、函数名和一对括号。如果适用,括号中包含方法所需的参数。您可以在此图表中看到语法。

fc609c15952551ce.png

在对象上调用 turnOn()turnOff() 方法

  1. main() 函数中,在 smartTvDevice 变量之后的行上,调用 turnOn() 方法。
fun main() {
    val smartTvDevice = SmartDevice()
    smartTvDevice.turnOn()
}
  1. turnOn() 方法之后的行上,调用 turnOff() 方法。
fun main() {
    val smartTvDevice = SmartDevice()
    smartTvDevice.turnOn()
    smartTvDevice.turnOff()
}
  1. 运行代码。

输出如下:

Smart device is turned on.
Smart device is turned off.

5. 定义类属性

在单元 1 中,您学习了变量,变量是单个数据片段的容器。您学习了如何使用 val 关键字创建只读变量,以及如何使用 var 关键字创建可变变量。

虽然方法定义了类可以执行的操作,但属性定义了类的特征或数据属性。例如,智能设备具有以下属性:

  • 名称。设备名称。
  • 类别。智能设备类型,例如娱乐、实用或烹饪。
  • 设备状态。设备是打开、关闭、在线还是离线。设备连接到互联网时被视为在线。否则被视为离线。

属性本质上是在类主体而不是函数主体中定义的变量。这意味着定义属性和变量的语法是相同的。您使用 val 关键字定义不可变属性,使用 var 关键字定义可变属性。

将上述特性实现为 SmartDevice 类的属性

  1. turnOn() 方法之前的行上,定义 name 属性并将其分配给字符串 "Android TV"
class SmartDevice {

    val name = "Android TV"

    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}
  1. name 属性之后的行上,定义 category 属性并将其分配给字符串 "Entertainment",然后定义一个 deviceStatus 属性并将其分配给字符串 "online"
class SmartDevice {

    val name = "Android TV"
    val category = "Entertainment"
    var deviceStatus = "online"

    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}
  1. smartTvDevice 变量之后的行上,调用 println() 函数,然后向其传递字符串 "Device name is: ${smartTvDevice.name}"
fun main() {
    val smartTvDevice = SmartDevice()
    println("Device name is: ${smartTvDevice.name}")
    smartTvDevice.turnOn()
    smartTvDevice.turnOff()
}
  1. 运行代码。

输出如下:

Device name is: Android TV
Smart device is turned on.
Smart device is turned off.

属性中的 getter 和 setter 函数

属性能做的比变量更多。例如,想象您创建了一个类结构来表示智能电视。您执行的常见操作之一是增大和减小音量。为了在编程中表示此操作,您可以创建一个名为 speakerVolume 的属性,它保存电视扬声器上设置的当前音量级别,但音量的值有一个范围。您可以设置的最小音量为 0,而最大音量为 100。为了确保 speakerVolume 属性永远不超过 100 或低于 0,您可以编写一个setter函数。当您更新属性的值时,您需要检查该值是否在 0 到 100 的范围内。再举一个例子,假设有一个要求是确保名称始终为大写。您可以实现一个getter函数将 name 属性转换为大写。

在深入了解如何实现这些属性之前,您需要了解声明它们的完整语法。定义可变属性的完整语法以变量定义开头,后跟可选的 get()set() 函数。您可以在此图表中看到语法。

f2cf50a63485599f.png

当您没有为属性定义 getter 和 setter 函数时,Kotlin 编译器会在内部创建这些函数。例如,如果您使用 var 关键字定义 speakerVolume 属性并为其分配 2 值,编译器会自动生成 getter 和 setter 函数,如此代码片段所示。

var speakerVolume = 2
    get() = field  
    set(value) {
        field = value    
    }

您不会在代码中看到这些行,因为它们是编译器在后台添加的。

不可变属性的完整语法有两个不同之处:

  • 它以 val 关键字开头。
  • val 类型的变量是只读变量,因此它们没有 set() 函数。

Kotlin 属性使用幕后字段来在内存中保存值。幕后字段本质上是在属性内部定义的类变量。幕后字段的作用域是属性,这意味着您只能通过 get()set() 属性函数访问它。

要在 get() 函数中读取属性值或在 set() 函数中更新值,您需要使用属性的幕后字段。它由 Kotlin 编译器自动生成,并使用 field 标识符引用。

例如,当您想要在 set() 函数中更新属性值时,您可以使用 set() 函数的参数,该参数被称为 value 参数,并将其分配给 field 变量,如此代码片段所示。

var speakerVolume = 2
    set(value) {
        field = value    
    }

例如,要确保分配给 speakerVolume 属性的值在 0 到 100 的范围内,您可以实现setter函数,如此代码片段所示。

var speakerVolume = 2
    set(value) {
        if (value in 0..100) {
            field = value
        }
    }

set() 函数使用 in 关键字后跟值范围来检查 Int 值是否在 0 到 100 的范围内。如果值在预期范围内,则更新 field 值。如果不在,属性值将保持不变。

您将在本 Codelab 的实现类之间的关系部分将此属性包含在类中,因此您现在无需将 setter 函数添加到代码中。

6. 定义构造函数

构造函数的主要目的是指定如何创建类的对象。换句话说,构造函数初始化对象并使对象可供使用。您在实例化对象时完成了此操作。构造函数中的代码在实例化类对象时执行。您可以定义带参数或不带参数的构造函数。

默认构造函数

默认构造函数是没有参数的构造函数。您可以定义一个默认构造函数,如此代码片段所示:

class SmartDevice constructor() {
    ...
}

Kotlin 旨在简洁,因此如果构造函数上没有注解或可见性修饰符(您很快就会学习到),您可以移除 constructor 关键字。如果构造函数没有参数,您也可以移除括号,如此代码片段所示:

class SmartDevice {
    ...
}

Kotlin 编译器会自动生成默认构造函数。您不会在代码中看到自动生成的默认构造函数,因为它是由编译器在后台添加的。

定义带参数的构造函数

SmartDevice 类中,namecategory 属性是不可变的。您需要确保 SmartDevice 类的所有实例都初始化 namecategory 属性。在当前的实现中,namecategory 属性的值是硬编码的。这意味着所有智能设备都使用字符串 "Android TV" 命名,并使用字符串 "Entertainment" 分类。

为了保持不可变性但避免硬编码值,使用带参数的构造函数来初始化它们。

  • SmartDevice 类中,将 namecategory 属性移到构造函数中,不分配默认值。
class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}

现在构造函数接受参数来设置其属性,因此实例化此类对象的方式也会改变。您可以在此图表中看到实例化对象的完整语法。

bbe674861ec370b6.png

这是代码表示:

SmartDevice("Android TV", "Entertainment")

传递给构造函数的两个参数都是字符串。有点不清楚哪个值应该赋给哪个参数。要解决此问题,类似于您传递函数参数的方式,您可以创建一个带命名参数的构造函数,如此代码片段所示:

SmartDevice(name = "Android TV", category = "Entertainment")

Kotlin 中有两种主要类型的构造函数:

  • 主构造函数。一个类只能有一个主构造函数,它被定义为类头的一部分。主构造函数可以是默认构造函数或带参数的构造函数。主构造函数没有主体。这意味着它不能包含任何代码。
  • 次构造函数。一个类可以有多个次构造函数。您可以定义带参数或不带参数的次构造函数。次构造函数可以初始化类并具有主体,其中可以包含初始化逻辑。如果类具有主构造函数,则每个次构造函数都需要初始化主构造函数。

您可以使用主构造函数在类头中初始化属性。传递给构造函数的参数会分配给属性。定义主构造函数的语法以类名开头,后跟 constructor 关键字和一对括号。括号中包含主构造函数的参数。如果参数多于一个,则用逗号分隔参数定义。您可以在此图表中看到定义主构造函数的完整语法。

aa05214860533041.png

次构造函数包含在类主体中,其语法包括三个部分:

  • 次构造函数声明。次构造函数定义以 constructor 关键字开头,后跟括号。如果适用,括号包含次构造函数所需的参数。
  • 主构造函数初始化。初始化以冒号开头,后跟 this 关键字和一对括号。如果适用,括号包含主构造函数所需的参数。
  • 次构造函数主体。主构造函数的初始化后跟一对大括号,其中包含次构造函数的主体。

您可以在此图表中看到语法。

2dc13ef136009e98.png

例如,想象您想要集成一个由智能设备提供商开发的 API。但是,该 API 返回 Int 类型的状态码以指示初始设备状态。如果设备离线,API 返回 0 值;如果设备在线,则返回 1 值。对于任何其他整数值,状态被视为未知。您可以在 SmartDevice 类中创建一个次构造函数,以将此 statusCode 参数转换为字符串表示形式,如此代码片段所示:

class SmartDevice(val name: String, val category: String) {
    var deviceStatus = "online"

    constructor(name: String, category: String, statusCode: Int) : this(name, category) {
        deviceStatus = when (statusCode) {
            0 -> "offline"
            1 -> "online"
            else -> "unknown"
        }
    }
    ...
}

7. 实现类之间的关系

继承使您可以在其他类的特征和行为的基础上构建一个类。它是一种强大的机制,可帮助您编写可重用代码并建立类之间的关系。

例如,市场上有许多智能设备,例如智能电视、智能灯和智能开关。在编程中表示智能设备时,它们共享一些共同的属性,例如名称、类别和状态。它们也具有共同的行为,例如打开和关闭的能力。

然而,每个智能设备的打开或关闭方式不同。例如,要打开电视,您可能需要打开显示屏,然后设置上次已知的音量级别和频道。另一方面,要打开灯,您可能只需要增大或减小亮度。

此外,每个智能设备都有更多的功能和操作可以执行。例如,使用电视,您可以调整音量和更改频道。使用灯,您可以调整亮度或颜色。

简而言之,所有智能设备都具有不同的功能,但共享一些共同的特性。您可以将这些共同特性复制到每个智能设备类中,也可以通过继承使代码可重用。

为此,您需要创建一个 SmartDevice 父类,并定义这些共同的属性和行为。然后,您可以创建子类,例如 SmartTvDeviceSmartLightDevice 类,它们继承父类的属性。

在编程术语中,我们说 SmartTvDeviceSmartLightDevice扩展SmartDevice 父类。父类也称为超类,子类称为子类。您可以在此图表中看到它们之间的关系。

Diagram representing inheritance relationship between classes.

然而,在 Kotlin 中,默认情况下所有类都是 final 的,这意味着您无法扩展它们,因此您必须定义它们之间的关系。

定义 SmartDevice 超类及其子类之间的关系

  1. SmartDevice 超类中,在 class 关键字之前添加一个 open 关键字,使其可扩展。
open class SmartDevice(val name: String, val category: String) {
    ...
}

open 关键字告知编译器该类是可扩展的,因此现在其他类可以扩展它。

创建子类的语法以创建类头开始,就像您迄今为止所做的那样。构造函数的结束括号后跟一个空格、一个冒号、另一个空格、超类名和一对括号。如果必要,括号包含超类构造函数所需的参数。您可以在此图表中看到语法。

1ac63b66e6b5c224.png

  1. 创建一个扩展 SmartDevice 超类的 SmartTvDevice 子类
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {
}

SmartTvDeviceconstructor 定义没有指定属性是可变的还是不可变的。这意味着 deviceNamedeviceCategory 参数只是 constructor 参数,而不是类属性。您将无法在类中使用它们,而只是将它们传递给超类构造函数。

  1. SmartTvDevice 子类的主体中,添加您在学习 getter 和 setter 函数时创建的 speakerVolume 属性。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }
}
  1. 定义一个 channelNumber 属性,并将其分配给一个 1 值,并带有一个指定范围为 0..200 的 setter 函数。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }
}
  1. 定义一个 increaseSpeakerVolume() 方法,该方法增加音量并打印字符串 "Speaker volume increased to $speakerVolume."
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

     var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    } 
}
  1. 添加一个 nextChannel() 方法,该方法增加频道号并打印字符串 "Channel number increased to $channelNumber."
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }
    
    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }
}
  1. SmartTvDevice 子类之后的行上,定义一个扩展 SmartDevice 超类的 SmartLightDevice 子类。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {
}
  1. SmartLightDevice 子类的主体中,定义一个 brightnessLevel 属性,并将其分配给一个 0 值,并带有一个指定范围为 0..100 的 setter 函数。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }
}
  1. 定义一个 increaseBrightness() 方法,该方法增加灯的亮度并打印字符串 "Brightness increased to $brightnessLevel."
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }
}

类之间的关系

当您使用继承时,您在两个类之间建立了一种称为IS-A 关系的关系。一个对象也是它所继承的类的一个实例。在HAS-A 关系中,一个对象可以拥有另一个类的实例,而无需实际成为该类的实例本身。您可以在此图表中看到这些关系的高级表示。

High level representation of HAS-A and IS-A relationship.

IS-A 关系

当您指定 SmartDevice 超类和 SmartTvDevice 子类之间的 IS-A 关系时,这意味着 SmartDevice 超类可以做的任何事情,SmartTvDevice 子类也可以做。这种关系是单向的,所以你可以说每台智能电视智能设备,但你不能说每个智能设备智能电视。IS-A 关系的代码表示如本代码片段所示:

// Smart TV IS-A smart device.
class SmartTvDevice : SmartDevice() {
}

不要仅仅为了实现代码复用而使用继承。在做决定之前,检查两个类之间是否存在关系。如果它们之间存在一些关系,检查它们是否真正符合 IS-A 关系。问问自己,“我可以把子类说成是超类吗?”。例如,Android 是一个操作系统。

HAS-A 关系

HAS-A 关系是指定两个类之间关系的另一种方式。例如,您很可能会在家里使用智能电视。在这种情况下,智能电视和家之间存在关系。家包含一个智能设备,或者换句话说,家拥有一台智能设备。HAS-A 关系在两个类之间也称为组合

到目前为止,您已经创建了几个智能设备。现在,您将创建包含智能设备的 SmartHome 类。SmartHome 类允许您与智能设备交互。

使用 HAS-A 关系定义 SmartHome

  1. SmartLightDevice 类和 main() 函数之间,定义一个 SmartHome 类。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    ...

}

class SmartHome {
}

fun main() { 
    ...
}
  1. SmartHome 类构造函数中,使用 val 关键字创建一个 SmartTvDevice 类型的 smartTvDevice 属性。
// The SmartHome class HAS-A smart TV device.
class SmartHome(val smartTvDevice: SmartTvDevice) {

}
  1. SmartHome 类的主体中,定义一个 turnOnTv() 方法,该方法调用 smartTvDevice 属性上的 turnOn() 方法。
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }
}
  1. turnOnTv() 方法之后的行上,定义一个 turnOffTv() 方法,该方法调用 smartTvDevice 属性上的 turnOff() 方法。
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        smartTvDevice.turnOff()
    }

}
  1. turnOffTv() 方法之后的行上,定义一个 increaseTvVolume() 方法,该方法调用 smartTvDevice 属性上的 increaseSpeakerVolume() 方法,然后定义一个 changeTvChannelToNext() 方法,该方法调用 smartTvDevice 属性上的 nextChannel() 方法。
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        smartTvDevice.turnOff()
    }

    fun increaseTvVolume() {
        smartTvDevice.increaseSpeakerVolume()
    }

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }
}
  1. SmartHome 类构造函数中,将 smartTvDevice 属性参数移到单独的行,后跟逗号。
class SmartHome(
    val smartTvDevice: SmartTvDevice,
) {

    ...

}
  1. smartTvDevice 属性之后的行上,使用 val 关键字定义一个 SmartLightDevice 类型的 smartLightDevice 属性。
// The SmartHome class HAS-A smart TV device and smart light.
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

}
  1. SmartHome 主体中,定义一个 turnOnLight() 方法,该方法调用 smartLightDevice 对象上的 turnOn() 方法,并定义一个 turnOffLight() 方法,该方法调用 smartLightDevice 对象上的 turnOff() 方法。
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }

    fun turnOnLight() {
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        smartLightDevice.turnOff()
    }
}
  1. turnOffLight() 方法之后的行上,定义一个 increaseLightBrightness() 方法,该方法调用 smartLightDevice 属性上的 increaseBrightness() 方法。
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }

    fun turnOnLight() {
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        smartLightDevice.turnOff()
    }

    fun increaseLightBrightness() {
        smartLightDevice.increaseBrightness()
    }
}
  1. increaseLightBrightness() 方法之后的行上,定义一个 turnOffAllDevices() 方法,该方法调用 turnOffTv()turnOffLight() 方法。
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

    fun turnOffAllDevices() {
        turnOffTv()
        turnOffLight()
    }
}

从子类重写超类方法

如前所述,尽管所有智能设备都支持打开和关闭功能,但它们执行功能的方式不同。为了提供这种特定于设备的行为,您需要重写超类中定义的 turnOn()turnOff() 方法。重写意味着拦截操作,通常是为了手动控制。当您重写一个方法时,子类中的方法会中断超类中定义的方法的执行,并提供自己的执行。

重写 SmartDevice 类的 turnOn()turnOff() 方法

  1. SmartDevice 超类的主体中,在每个方法的 fun 关键字之前,添加一个 open 关键字。
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    open fun turnOn() {
        // function body
    }

    open fun turnOff() {
        // function body
    }
}
  1. SmartLightDevice 类的主体中,定义一个空的 turnOn() 方法。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    fun turnOn() {
    }
}
  1. turnOn() 方法的主体中,将 deviceStatus 属性设置为字符串 "on",将 brightnessLevel 属性设置为值 2,并添加一个 println() 语句,然后向其传递字符串 "$name turned on. The brightness level is $brightnessLevel."
    fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }
  1. SmartLightDevice 类的主体中,定义一个空的 turnOff() 方法。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    fun turnOff() {
    }
}
  1. turnOff() 方法的主体中,将 deviceStatus 属性设置为字符串 "off",将 brightnessLevel 属性设置为值 0,并添加一个 println() 语句,然后向其传递字符串 "Smart Light turned off"
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    fun turnOff() {
        deviceStatus = "off"
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}
  1. SmartLightDevice 子类中,在 turnOn()turnOff() 方法的 fun 关键字之前,添加 override 关键字。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    override fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    override fun turnOff() {
        deviceStatus = "off"
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}

override 关键字通知 Kotlin 运行时执行子类中定义的方法中包含的代码。

  1. SmartTvDevice 类的主体中,定义一个空的 turnOn() 方法。
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }
        
    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }
        
    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }
    
    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    fun turnOn() {
    }
}
  1. turnOn() 方法的主体中,将 deviceStatus 属性设置为字符串 "on",并添加一个 println() 语句,然后向其传递字符串 "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " + "set to $channelNumber."
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    fun turnOn() {
        deviceStatus = "on"
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }
}
  1. SmartTvDevice 类中,在 turnOn() 方法之后,定义一个空的 turnOff() 方法。
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    fun turnOn() {
        ...
    }

    fun turnOff() {
    }
}
  1. turnOff() 方法的主体中,将 deviceStatus 属性设置为字符串 "off",并添加一个 println() 语句,然后向其传递字符串 "$name turned off"
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    fun turnOn() {
        ...
    }

    fun turnOff() {
        deviceStatus = "off"
        println("$name turned off")
    }
}
  1. SmartTvDevice 类中,在 turnOn()turnOff() 方法的 fun 关键字之前,添加 override 关键字。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    override fun turnOn() {
        deviceStatus = "on"
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }

    override fun turnOff() {
        deviceStatus = "off"
        println("$name turned off")
    }
}
  1. main() 函数中,使用 var 关键字定义一个 SmartDevice 类型的 smartDevice 变量,该变量实例化一个 SmartTvDevice 对象,该对象接受 "Android TV" 参数和 "Entertainment" 参数。
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
}
  1. smartDevice 变量之后的行上,调用 smartDevice 对象上的 turnOn() 方法。
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
    smartDevice.turnOn()
}
  1. 运行代码。

输出如下:

Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
  1. 在调用 turnOn() 方法之后的行上,将 smartDevice 变量重新分配以实例化一个 SmartLightDevice 类,该类接受 "Google Light" 参数和 "Utility" 参数,然后调用 smartDevice 对象引用上的 turnOn() 方法。
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
    smartDevice.turnOn()
    
    smartDevice = SmartLightDevice("Google Light", "Utility")
    smartDevice.turnOn()
}
  1. 运行代码。

输出如下:

Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
Google Light turned on. The brightness level is 2.

这是多态性的一个例子。代码在 SmartDevice 类型的变量上调用 turnOn() 方法,并且根据变量的实际值,可以执行 turnOn() 方法的不同实现。

使用 super 关键字在子类中重用超类代码

当您仔细查看 turnOn()turnOff() 方法时,您会注意到在 SmartTvDeviceSmartLightDevice 子类中调用这些方法时,deviceStatus 变量的更新方式相似:代码重复了。您可以在 SmartDevice 类中更新状态时重用代码。

要从子类调用超类中被重写的方法,您需要使用 super 关键字。从超类调用方法类似于从类外部调用方法。您需要使用 super 关键字,而不是在对象和方法之间使用 . 运算符,这会通知 Kotlin 编译器调用超类而不是子类中的方法。

从超类调用方法的语法以 super 关键字开头,后跟 . 运算符、函数名和一对括号。如果适用,括号中包含参数。您可以在此图表中看到语法。

18cc94fefe9851e0.png

重用 SmartDevice 超类中的代码

  1. turnOn()turnOff() 方法中移除 println() 语句,并将重复的代码从 SmartTvDeviceSmartLightDevice 子类移动到 SmartDevice 超类。
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    open fun turnOn() {
        deviceStatus = "on"
    }

    open fun turnOff() {
        deviceStatus = "off"
    }
}
  1. SmartTvDeviceSmartLightDevice 子类中使用 super 关键字调用 SmartDevice 类中的方法。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

     var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    override fun turnOn() {
        super.turnOn()
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }

    override fun turnOff() {
        super.turnOff()
        println("$name turned off")
    }
}
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    override fun turnOn() {
        super.turnOn()
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    override fun turnOff() {
        super.turnOff()
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}

从子类重写超类属性

与方法类似,您也可以按照相同的步骤重写属性。

重写 deviceType 属性

  1. SmartDevice 超类中,在 deviceStatus 属性之后的行上,使用 openval 关键字定义一个 deviceType 属性,并将其设置为字符串 "unknown"
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    open val deviceType = "unknown"
    ...
}
  1. SmartTvDevice 类中,使用 overrideval 关键字定义一个 deviceType 属性,并将其设置为字符串 "Smart TV"
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    ...
}
  1. SmartLightDevice 类中,使用 overrideval 关键字定义一个 deviceType 属性,并将其设置为字符串 "Smart Light"
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart Light"

    ...

}

8. 可见性修饰符

可见性修饰符在实现封装方面发挥着重要作用。

  • 中,它们允许您将属性和方法隐藏起来,以免受到类外部的未经授权的访问。
  • 中,它们允许您将类和接口隐藏起来,以免受到包外部的未经授权的访问。

Kotlin 提供四种可见性修饰符:

  • public默认可见性修饰符。使声明可在任何地方访问。您希望在类外部使用的属性和方法被标记为 public。
  • private使声明可在同一类或源文件中访问。

可能有一些属性和方法仅在类内部使用,并且您不一定希望其他类使用它们。可以使用 private 可见性修饰符标记这些属性和方法,以确保其他类不会意外地访问它们。

  • protected使声明可在子类中访问。您希望在其定义类及其子类中使用的属性和方法被标记为 protected 可见性修饰符。
  • internal使声明可在同一模块中访问。内部修饰符类似于 private,但只要在同一模块中访问,就可以从类外部访问 internal 属性和方法。

当您定义一个类时,它是公共可见的,可以被任何导入它的包访问,这意味着它默认是公共的,除非您指定可见性修饰符。类似地,当您在类中定义或声明属性和方法时,默认情况下可以通过类对象在类外部访问它们。为代码定义适当的可见性至关重要,主要是为了隐藏其他类不需要访问的属性和方法。

例如,考虑汽车如何供驾驶员使用。组成汽车的零件以及汽车内部如何工作的具体细节默认是隐藏的。汽车旨在尽可能直观地操作。您不会希望一辆汽车像商用飞机一样复杂,这类似于您不希望其他开发人员或未来的您对类中的哪些属性和方法应该使用感到困惑。

可见性修饰符可帮助您向项目中的其他类展示代码的相关部分,并确保实现不会被无意中使用,从而使代码易于理解且不易出错。

可见性修饰符应在声明类、方法或属性时放在声明语法之前,如此图所示:

dcc4f6693bf719a9.png

指定属性的可见性修饰符

指定属性可见性修饰符的语法以 privateprotectedinternal 修饰符开头,后跟定义属性的语法。您可以在此图表中看到语法。

47807a890d237744.png

例如,您可以在此代码片段中看到如何将 deviceStatus 属性设为私有:

open class SmartDevice(val name: String, val category: String) {

    ...

    private var deviceStatus = "online"

    ...
}

您还可以为 setter 函数设置可见性修饰符。修饰符放在 set 关键字之前。您可以在此图表中看到语法。

cea29a49b7b26786.png

对于 SmartDevice 类,应通过类对象在类外部读取 deviceStatus 属性的值。然而,只有类及其子类才能更新或写入该值。要实现此要求,您需要对 deviceStatus 属性的 set() 函数使用 protected 修饰符。

deviceStatus 属性的 set() 函数上使用 protected 修饰符

  1. SmartDevice 超类的 deviceStatus 属性中,向 set() 函数添加 protected 修饰符。
open class SmartDevice(val name: String, val category: String) {

    ...

    var deviceStatus = "online"
        protected set(value) {
           field = value
       }

    ...
}

您未在 set() 函数中执行任何操作或检查。您只是将 value 参数分配给 field 变量。正如您之前所学,这类似于属性 setter 的默认实现。在这种情况下,您可以省略 set() 函数的括号和主体。

open class SmartDevice(val name: String, val category: String) {

    ...

    var deviceStatus = "online"
        protected set

    ...
}
  1. SmartHome 类中,定义一个 deviceTurnOnCount 属性,将其设置为 0 值,并带有一个 private setter 函数。
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    var deviceTurnOnCount = 0
        private set

    ...
}
  1. deviceTurnOnCount 属性后跟 ++ 算术运算符添加到 turnOnTv()turnOnLight() 方法中,然后将 deviceTurnOnCount 属性后跟 -- 算术运算符添加到 turnOffTv()turnOffLight() 方法中。
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    var deviceTurnOnCount = 0
        private set

    fun turnOnTv() {
        deviceTurnOnCount++
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        deviceTurnOnCount--
        smartTvDevice.turnOff()
    }
    
    ...

    fun turnOnLight() {
        deviceTurnOnCount++
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        deviceTurnOnCount--
        smartLightDevice.turnOff()
    }

    ...

}

方法的可见性修饰符

指定方法可见性修饰符的语法以 privateprotectedinternal 修饰符开头,后跟定义方法的语法。您可以在此图表中看到语法。

e0a60ddc26b841de.png

例如,您可以在此代码片段中看到如何在 SmartTvDevice 类中为 nextChannel() 方法指定 protected 修饰符。

class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    protected fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }      

    ...
}

构造函数的可见性修饰符

指定构造函数可见性修饰符的语法类似于定义主构造函数,但有几个不同之处:

  • 修饰符指定在类名之后,但在 constructor 关键字之前。
  • 如果您需要为主构造函数指定修饰符,即使没有参数,也必须保留 constructor 关键字和括号。

您可以在此图表中看到语法。

6832575eba67f059.png

例如,您可以在此代码片段中看到如何在 SmartDevice 构造函数中添加 protected 修饰符。

open class SmartDevice protected constructor (val name: String, val category: String) {

    ...

}

类的可见性修饰符

指定类可见性修饰符的语法以 privateprotectedinternal 修饰符开头,后跟定义类的语法。您可以在此图表中看到语法。

3ab4aa1c94a24a69.png

例如,您可以在此代码片段中看到如何在 SmartDevice 类中指定 internal 修饰符。

internal open class SmartDevice(val name: String, val category: String) {

    ...

}

理想情况下,您应尽量严格控制属性和方法的可见性,因此应尽可能频繁地使用 private 修饰符声明它们。如果不能保持 private,请使用 protected 修饰符。如果不能保持 protected,请使用 internal 修饰符。如果不能保持 internal,请使用 public 修饰符。

指定适当的可见性修饰符

此表可帮助您根据类或构造函数的属性或方法应在哪里可访问来确定适当的可见性修饰符。

修饰符

在同一类中可访问

在子类中可访问

在同一模块中可访问

在模块外部可访问

private

🗙

🗙

🗙

protected

🗙

🗙

internal

🗙

public

SmartTvDevice 子类中,您不应允许从类外部控制 speakerVolumechannelNumber 属性。这些属性应仅通过 increaseSpeakerVolume()nextChannel() 方法控制。

类似地,在 SmartLightDevice 子类中,brightnessLevel 属性应仅通过 increaseLightBrightness() 方法控制。

SmartTvDeviceSmartLightDevice 子类添加适当的可见性修饰符。

  1. SmartTvDevice 类中,为 speakerVolumechannelNumber 属性添加 private 可见性修饰符。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    private var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    private var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    ...
}
  1. SmartLightDevice 类中,为 brightnessLevel 属性添加 private 修饰符。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    private var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    ...
}

9. 定义属性委托

您在上一节中学到,Kotlin 中的属性使用幕后字段在内存中保存其值。您使用 field 标识符来引用它。

当您查看目前的代码时,您可以看到 SmartTvDeviceSmartLightDevice 类中 speakerVolumechannelNumberbrightnessLevel 属性的重复代码,用于检查值是否在范围内。您可以使用委托在 setter 函数中重用范围检查代码。委托管理值,而不是使用字段以及 getter 和 setter 函数。

创建属性委托的语法以变量声明开始,后跟 by 关键字以及处理属性的 getter 和 setter 函数的委托对象。您可以在此图表中看到语法。

928547ad52768115.png

在实现您可以委托实现的类之前,您需要熟悉接口。接口是一种契约,实现它的类需要遵守。它侧重于做什么而不是如何做这个动作。简而言之,接口可帮助您实现抽象

例如,在您建造房子之前,您告诉建筑师您想要什么。您想要一间卧室、儿童房、客厅、厨房和几个浴室。简而言之,您指定了您想要什么,而建筑师指定了如何实现。您可以在此图表中看到创建接口的语法。

bfe3fd1cd8c45b2a.png

您已经学习了如何扩展类并重写其功能。使用接口,类实现接口。类为接口中声明的方法和属性提供实现细节。您将使用 ReadWriteProperty 接口做类似的事情来创建委托。您将在下一个单元中了解更多关于接口的内容。

要为 var 类型创建委托类,您需要实现 ReadWriteProperty 接口。类似地,您需要为 val 类型实现 ReadOnlyProperty 接口。

var 类型创建委托

  1. main() 函数之前,创建一个实现 ReadWriteProperty<Any?, Int> 接口的 RangeRegulator 类。
class RangeRegulator() : ReadWriteProperty<Any?, Int> {

}

fun main() {
    ...
}

不用担心尖括号或其中的内容。它们表示泛型类型,您将在下一个单元中了解它们。

  1. RangeRegulator 类的主构造函数中,添加一个 initialValue 参数,一个 private minValue 属性,以及一个 private maxValue 属性,所有这些属性的类型都是 Int
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

}
  1. RangeRegulator 类的主体中,重写 getValue()setValue() 方法。
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}

这些方法充当属性的 getter 和 setter 函数。

  1. SmartDevice 类之前的行上,导入 ReadWritePropertyKProperty 接口。
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

open class SmartDevice(val name: String, val category: String) {
    ...
}

...

class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}

...
  1. RangeRegulator 类中,在 getValue() 方法之前的行上,定义一个 fieldData 属性,并使用 initialValue 参数对其进行初始化。
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}

此属性充当变量的幕后字段

  1. getValue() 方法的主体中,返回 fieldData 属性。
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}
  1. setValue() 方法的主体中,在将 value 参数赋值给 fieldData 属性之前,检查分配的值是否在 minValue..maxValue 范围内。
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value in minValue..maxValue) {
            fieldData = value
        }
    }
}
  1. SmartTvDevice 类中,使用委托类定义 speakerVolumechannelNumber 属性。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)

    private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)

    ...

}
  1. SmartLightDevice 类中,使用委托类定义 brightnessLevel 属性。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart Light"

    private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)

    ...

}

10. 测试解决方案

您可以在此代码片段中看到解决方案代码。

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"
        protected set

    open val deviceType = "unknown"

    open fun turnOn() {
        deviceStatus = "on"
    }

    open fun turnOff() {
        deviceStatus = "off"
    }
}

class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)

    private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    override fun turnOn() {
        super.turnOn()
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }

    override fun turnOff() {
        super.turnOff()
        println("$name turned off")
    }
}

class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart Light"

    private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    override fun turnOn() {
        super.turnOn()
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    override fun turnOff() {
        super.turnOff()
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}

class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    var deviceTurnOnCount = 0
        private set

    fun turnOnTv() {
        deviceTurnOnCount++
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        deviceTurnOnCount--
        smartTvDevice.turnOff()
    }

    fun increaseTvVolume() {
        smartTvDevice.increaseSpeakerVolume()
    }

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }

    fun turnOnLight() {
        deviceTurnOnCount++
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        deviceTurnOnCount--
        smartLightDevice.turnOff()
    }

    fun increaseLightBrightness() {
        smartLightDevice.increaseBrightness()
    }

    fun turnOffAllDevices() {
        turnOffTv()
        turnOffLight()
    }
}

class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value in minValue..maxValue) {
            fieldData = value
        }
    }
}

fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
    smartDevice.turnOn()

    smartDevice = SmartLightDevice("Google Light", "Utility")
    smartDevice.turnOn()
}

输出如下:

Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
Google Light turned on. The brightness level is 2.

11. 尝试此挑战

  • SmartDevice 类中,定义一个 printDeviceInfo() 方法,该方法打印字符串 "Device name: $name, category: $category, type: $deviceType"
  • SmartTvDevice 类中,定义一个 decreaseVolume() 方法,该方法减小音量,并定义一个 previousChannel() 方法,该方法切换到上一个频道。
  • SmartLightDevice 类中,定义一个 decreaseBrightness() 方法,该方法减小亮度。
  • SmartHome 类中,确保只有当每个设备的 deviceStatus 属性设置为字符串 "on" 时,才能执行所有操作。此外,确保 deviceTurnOnCount 属性已正确更新。

完成实现后

  • SmartHome 类中,定义 decreaseTvVolume()changeTvChannelToPrevious()printSmartTvInfo()printSmartLightInfo()decreaseLightBrightness() 方法。
  • SmartHome 类中调用 SmartTvDeviceSmartLightDevice 类中适当的方法。
  • main() 函数中,调用这些添加的方法进行测试。

12. 结论

恭喜!您学习了如何定义类和实例化对象。您还学习了如何创建类之间的关系和创建属性委托。

总结

  • OOP 有四个主要原则:封装、抽象、继承和多态。
  • 类使用 class 关键字定义,并包含属性和方法。
  • 属性类似于变量,只是属性可以有自定义的 getter 和 setter。
  • 构造函数指定如何实例化类的对象。
  • 定义主构造函数时可以省略 constructor 关键字。
  • 继承使代码重用更容易。
  • IS-A 关系指继承。
  • HAS-A 关系指组合。
  • 可见性修饰符在实现封装方面发挥重要作用。
  • Kotlin 提供四种可见性修饰符:publicprivateprotectedinternal 修饰符。
  • 属性委托允许您在多个类中重用 getter 和 setter 代码。

了解更多