Menu
CoddyTech

泛型与 UseMethod

本课属于 Coddy R 学习旅程的“面向对象编程”单元。第 7 课,共 57 课。

泛型是函数体为 UseMethod("name") 的函数。调用它时,它会查看第一个参数的类,并调用 name.class 方法。print() 和 format() 都是这样工作的;你也可以编写自己的泛型:

speak <- function(x) UseMethod("speak")
speak.dog <- function(x) paste(x$name, "says woof")
speak.cat <- function(x) paste(x$name, "says meow")
rex <- structure(list(name = "Rex"), class = "dog")
tom <- structure(list(name = "Tom"), class = "cat")
print(speak(rex))
print(speak(tom))

输出:

[1] "Rex says woof"
[1] "Tom says meow"

调用者只需写一次调用,每个对象都会以自己的方式响应。这就是多态:遍历混合列表的代码不需要为每个类都写一个 if:

speak <- function(x) UseMethod("speak")
speak.dog <- function(x) paste(x$name, "says woof")
speak.cat <- function(x) paste(x$name, "says meow")
pets <- list(structure(list(name = "Rex"), class = "dog"),
             structure(list(name = "Tom"), class = "cat"),
             structure(list(name = "Bo"), class = "dog"))
for (p in pets) cat(speak(p), "\n")

输出:

Rex says woof 
Tom says meow 
Bo says woof 

方法会接收泛型函数的参数。给泛型函数添加一个 ... 参数,这样方法就能接受自己的额外参数,例如圆形的 digits:

area <- function(shape, ...) UseMethod("area")
area.square <- function(shape, ...) shape$side^2
area.circle <- function(shape, digits = 2, ...) round(pi * shape$r^2, digits)
sq <- structure(list(side = 3), class = "square")
ci <- structure(list(r = 1), class = "circle")
print(area(sq))
print(area(ci))
print(area(ci, digits = 4))

输出:

[1] 9
[1] 3.14
[1] 3.1416

UseMethod() 会把调用交给方法,并且不会返回:方法返回的内容就是泛型的结果,UseMethod() 之后的代码不会运行。因此,泛型只有一行:

size <- function(x) {
  UseMethod("size")
  cat("this line never runs\n")
}
size.box <- function(x) x$w * x$h
b <- structure(list(w = 2, h = 5), class = "box")
print(size(b))

输出:

[1] 10
challenge icon

挑战

简单

形状分布在 3 个文件中。完成通用函数 area(),将它写入 Shape.R,然后在 Circle.R 中添加方法 area.circle()(圆周率乘以半径的平方),并在 Rectangle.R 中添加方法 area.rectangle()(宽乘以高)。构造函数已给出。

提供的代码会读取 circle 2 或 rectangle 3 4 这样的行,并输出每个形状的类和面积,保留两位小数:circle: 12.57。

你的代码要写在 Shape.R、Circle.R 和 Rectangle.R 中。main.R 中包含提供的输入/输出代码,不能编辑。

亲手试试

source("Shape.R")
source("Circle.R")
source("Rectangle.R")

# 提供的输入输出代码:保持原样
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
  p <- strsplit(line, " ")[[1]]
  s <- if (p[1] == "circle") new_circle(as.numeric(p[2])) else new_rectangle(as.numeric(p[2]), as.numeric(p[3]))
  cat(sprintf("%s: %.2f", class(s), area(s)), "\n", sep = "")
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: R 在线编译器