基于S3的面向对象编程

Jeason

1105字约4分钟

2024-09-23

S3对象的介绍

在R语言中,基于S3对象的面向对象编程,是一种基于泛型函数的实现方式。泛型函数是一种特殊的函数,根据传入对象的类型决定调用那个具体的方法。基于S3对象实现面向对象编程,不同其他语言的面型对象编程,是一种动态函数调用的模拟实现。S3对象被广泛应用于R的早期的开发包中。

创建S3对象

S3对象的创建非常简单,只需要把相应的属性添加到class即可,主要通过class来区分S3对象

x <- 1
attr(x,'class') <- 'foo'

## [1] 1
## attr(,"class")
## [1] "foo"

attr(x,"class")

## [1] "foo"

class(x)

## [1] "foo"

通过structure()函数也可以创建S3对象

y <- structure(2,class="foo")
y

## [1] 2
## attr(,"class")
## [1] "foo"

attr(y,"class")

## [1] "foo"

class(x)

## [1] "foo"

创建一个多类型的S3对象,S3独享没有明确结构关系,一个S3对象可以有多个类型,S3对象的class属性可以是一个,也可以包括多种类型

x <- 1
attr(x,"class") <- c("foo","bar")
class(x)

## [1] "foo" "bar"

泛型函数和方法调用

对于S3对象的使用,通常用UseMethod()函数来定义一个泛型函数的名称,通过传入参数的class属性,来确定方法调用。

简单的可以通过泛型函数完成以下任务:

  • UseMethod()定义teacher泛型函数
  • teacher.xxx的语法格式定义teacher对象的行为
  • 其中teacher.default是默认行为

# 用UseMethod()定义teacher泛型函数
teacher <- function(x,...) UseMethod("teacher")

# 定义teacher内部函数
teacher.default <- function(x,...) print("你不是teacher")
teacher.correcting <- function(x,...) print("批改作业")

方法调用通过传入参数的class属性,来确定不同方法调用

  • 定义一个变量a,并设置aclass属性为correcting
  • 把变量a传入到teacher泛型函数中
  • 函数teacher.correcting()函数的行为被调用
a <- "teacher"
attr(a,"class") <- 'correcting'
teacher(a)

[1]批改作业

teacher()

[1] "你不是teacher"

假如想要对内部函数添加更多的参数用于完成更特定的任务,则可在内部函数中直接添加特定的参数

teacher.assignment <- function(x, name = '小明',...) print(paste0("布置作业给", name))

a <- "teacher"
attr(a,"class") <- 'assignment'
teacher(a, '小明')

[1] "布置作业给小明"

注意

  • 在书写内部函数时需要注意,内部函数中的参数顺序必须与泛型函数中的参数顺序保持一致,即A method must have arguments in exactly the same order as the generic.
  • 在R package中使用泛型函数时,如果内部函数之间的参数与泛型函数中的参数个数不一致时会出现warning,此时建议只在泛型函数中参加必要的参数和...参数,可以避免warning出现

查看S3对象的函数

当我们使用S3进行面向对象封装后,可以使用methods()函数来查看S3对象中的定义的内部行为函数。

# 查看teacher对象
teacher
function(x,...) Usemethod("teacher")

# 查看teacher对象的内部函数
methods(teacher)
[1] teacher.assignment teacher.correcting teacher.default teacher.lecture

#通过methods()的generic.function参数,来匹配泛型函数名字
methods(generic.function = predict)
[1] predict.ar* ......

通过methods()class参数,来匹配类的名字

methods(class=lm)
[1]add1.lm* ......

getAnywhere()函数,查看所有函数

#查看teacher.lecture函数

>getAnywhere(teacher.correcting)

使用getS3method()函数,也同样可以查看不可见的函数

# getS3method()函数查找predict.ppr

getS3method("predict","ppr")

S3对象的继承关系

S3独享有一种非常简单的继承方式,用NextMethod()函数来实现。

node <- function(x) UseMethod("node",x)
node.default <- function(x) "Default node"

#father函数
node.father <- function(x) c("father")

# son函数,通过NextMethod()函数继承father函数
node.son <- function(x) c('son',NextMethod())

#定义n1
n1 <- structure(1,class=c("father"))
# 在node函数中传入n1,执行node.father()函数
node(n1)
[1] "father"

# 定义n2,设置class属性为两个
n2 <- structure(1,class=c("son","father"))
# 在node函数中传入n2,执行node.son()函数和node.father()函数
node(n2)
[1] "son" "father"

通过对node()函数传入n2的参数,node.son()先被执行,然后通过NextMethod()函数继续执行了node.father()函数。这样其实就模拟了,子函数调用父函数的过程,实现了面向对象编程中的继承。