跳至主要内容

R 中的对象与类

学习面向对象编程(OOP)、R 的对象、S3 与 S4 等不同类及其构建方式、如何创建泛型函数示例等内容。
· 9分钟 阅读

使用 AI 探索

面向对象编程(OOP)是一种编程范式,它使用不同的方法围绕数据或对象来设计软件,而不是仅靠函数。在 OOP 语境中,方法其实就是函数。之所以称为面向对象,是因为所有处理都围绕对象及其字段展开。每个对象都有不同的属性和行为。

在面向对象编程中,整个程序可以被分成不同的类,从而更快理解程序的执行流程。R 是一门函数式编程语言,引入 OOP 有助于管理大型系统问题。OOP 常用于管理 GUI 应用,尤其是 Web 应用。面向对象编程非常适合构建数据分析工具,但并不适合直接进行数据分析本身。

OOP 的体系

主要有两种 OOP 体系,如下所述:

  • S3 类:支持对函数进行重载。
  • S4 类:可以对数据进行更严格的约束,但程序调试相对更难。

注意:这些类将在后文详细讨论。

R 中的对象

对象是类的实例。此外,在 R 中一切皆对象,欲了解更多,请参阅 R 的数据类型。对象也可以拥有诸如 class、attributes、dimnnames、names 等属性。

例如,对象通过 <-, 进行赋值。请看下面的代码片段,其中 “a1” 和 “a2” 是变量,被赋予数值。

a1 <- 10
a2 <- 20

对象也可以被赋为一组数字,其中 “a3” 是一个包含多个数字的向量对象。

a3 <- c(10,20,30)

这里的 c 将 10、20、30 组合成一个向量。

让我们来看一个真实场景:学生这个对象拥有姓名、年龄、年级和性别等属性;老师这个对象拥有姓名、年龄和性别等属性。

st_name <- "andrew"
st_age <- 16
st_class <- 10
st_gender <- "male"

t_name <- "angelina"
t_age <- 26
t_gender <- "female"

student <- c(st_name, st_age, st_class, st_gender)
teacher <- c(t_name, t_age, t_gender)


print(student)
print(teacher)
[1] "andrew" "16"     "10"     "male"  
[1] "angelina" "26"       "female"   

上述代码会得到如下输出:

"andrew" "16" "10" "male"  
"angelina" "26"  "female"

从上面的示例可以看到,不同类型的对象。“student” 和 “teacher” 对象(也称变量)分别包含类型为 “character” 和 “double” 的值。

R 中的类

类是用于创建对象的蓝图,包含其成员变量及属性。

正如前文所述,R 有两种类体系:S3 和 S4。

S3 类

这些类通过将函数拆分为泛型与方法,从而实现函数重载。

函数 = 泛型 + 方法

S3 与 C#、Java 等其他编程语言非常不同。它实现起来非常简单,仅使用第一个参数进行分发。因此,从程序员角度看,它不需要太多预备知识。

S3 通过严格的命名约定 generic.dot.class 来工作。

                             generic.class

方法的参数应包含泛型的参数,且方法应包含 ...arg。

构建 S3 类及示例

下面的示例将演示如何构建一个 S3 类。示例展示了创建列表、传入组件、设置合适的类名,并打印结果。

如下所示,“studentBio” 是一个列表,组件以列表形式传入,分别为 “student_name”、“student_age” 和 “student_contact”。类名为 “Student Info”,“studentBio” 为新建的类实例,其值如下所示。

studentBio <- list(studentName = "Harry Potter", studentAge = 19, studentContact="London")
class(studentBio) <- "StudentInfo"
studentBio
$studentName
[1] "Harry Potter"

$studentAge
[1] 19

$studentContact
[1] "London"

attr(,"class")
[1] "StudentInfo"

上述代码将输出如下内容:

$studentName
 "Harry Potter"

$studentAge
19

$studentContact
"London"

attr(,"class")
"StudentInfo"

如果您对在 R 中编写函数还不熟悉,请阅读 R 函数使用教程!

在 S3 中创建自定义泛型方法

您将使用下面的代码实现一个泛型函数。

下面的代码展示了名为 “contact” 的泛型函数,将对象作为参数传入。同时,使用 “UseMethod” 与名为 “contact” 的泛型函数配合,以实现方法的“分发”,即根据传入参数的不同返回不同结果的过程。

contact <- function(object) {
UseMethod("contact")
}

接下来为先前创建的 “StudentInfo” 类编写一个对应的方法。方法名为 “contact”,后接点号与类名 “StudentInfo”。在函数体内,通过 “$” 访问属性 “studentContact”。

contact.StudentInfo <- function(object) {
cat("Your contact is", object$studentContact, "\n")
}
contact(studentBio)
Your contact is London

上述代码使用 object$studentContact,即将对象作为参数传入函数,并借助 “$” 访问属性 “studentContact”,输出结果如下。

Your contact is London

S4 类

S4 类更为严格、规范,更贴近面向对象概念。S4 的类由正式的类定义来表示。更具体地说,S4 为方法和泛型提供了 setter 和 getter 函数。与 S3 类相比,S4 能够支持多重分发。

下面将通过创建并讨论示例,来具体了解 R 中上述类。

S4 类通过 set class 函数定义。

 setClass()

构建 S4 类及示例

如前所述,S4 类通过 setClass() 方法定义。您将使用 setClass() 方法创建并定义 S4 类。我们将使用一个子程序来验证数据一致性(验证),并设置默认值(原型)。

以下为定义类及插槽的代码:

下面用于定义一个类(也称为 “prototype”),其中定义了插槽:类名为 “employee”,name 为 character,id 为 numeric,contact 为 character。

setClass("employee", slots=list(name="character", id="numeric", contact="character"))

以下代码可用于创建 S4 的具体实例或对象。

使用 “new” 并在其中指定类名 “employee”,为 name、id 和 contact 等插槽填充值,从而创建对象。

obj <- new("employee",name="Steven", id=1002, contact="West Avenue")
obj
An object of class "employee"
Slot "name":
[1] "Steven"

Slot "id":
[1] 1002

Slot "contact":
[1] "West Avenue"
An object of class "employee"
Slot "name":
"Steven"

Slot "id":
1002

Slot "contact":
"West Avenue"

以上输出表明,所创建的对象属于 "employee" 类;其 Slot "name" 为 "Steven",Slot "id" 值为 1002,Slot "contact" 值为 "West Avenue"。

函数与命令

要检查某对象是否为 S4 对象,可使用以下两个命令:

命令 ‘is.object(obj)’ 用于检查某变量或实例是否引用一个对象。

is.object(obj)

TRUE

上述代码将输出如下结果:

TRUE

命令 ‘is.S4(obj)’ 用于检查某变量或实例是否为 S4 对象。

isS4(obj)

TRUE

上述代码将输出如下结果:

TRUE

访问与修改插槽值

访问插槽

插槽通过 "@" 访问,而不是使用 "$"。

使用 ‘obj@name’ 即对象名加插槽名来访问名称 “Steven”,‘obj@id’ 同理。

obj@name
obj@id

'Steven'

1002

上述代码将得到如下输出:

'Steven'
1002

修改插槽

使用赋值运算符("<-")即可修改插槽值,例如将 ‘obj@contact’ 赋为新值 "North Avenue"。

obj@contact <- "North Avenue"
obj
An object of class "employee"
Slot "name":
[1] "Steven"

Slot "id":
[1] 1002

Slot "contact":
[1] "North Avenue"

上述代码将得到如下输出:

An object of class "employee"
Slot "name":
"Steven"

Slot "id":
1002

Slot "contact":
"North Avenue"

从以上输出可见,Slot "contact" 的值已由先前的 "West Avenue" 改为 "North Avenue"。

另外,也可使用 ‘slot()’ 来修改值。

‘slot(obj, "id")’ 表示获取插槽 "id",并将其赋值为 1004,随后用 ‘obj’ 打印结果。

slot(obj,"id") <- 1004
obj
An object of class "employee"
Slot "name":
[1] "Steven"

Slot "id":
[1] 1004

Slot "contact":
[1] "North Avenue"
An object of class "employee"
Slot "name":
"Steven"

Slot "id":
1004

Slot "contact":
"North Avenue"

以上输出显示,Slot "id" 的值已由 1002 改为 1004。

在 S4 中创建自定义泛型方法

我们来创建一个函数,使其按需打印输出。使用 ‘setMethod()’ 与泛型函数 ‘show()’。同时,通过 ‘@’ 访问所有插槽的值并打印其名称。最后,通过 ‘obj’ 获取输出。

setMethod("show",
"employee",
function(object) {
cat("Name:",object@name, "\n")
cat("Id:",object@id, "\n")
cat("Contact:", object@contact, "\n")
}
)
obj
Name: Steven
Id: 1004
Contact: North Avenue
Name: Steven
Id: 1004
Contact: North Avenue

以上输出表明,结果以插槽名及其对应值的形式打印。

恭喜

恭喜您,已完成本教程!

在本教程中,您学习了面向对象编程(OOP)、R 的对象、S3 与 S4 等不同类及其构建方式,并通过示例了解了如何创建泛型函数等内容。

如果您想进一步学习 R,请查看 DataCamp 的课程:

Object-Oriented Programming with S3 and R6 in R

主题