Menu
CoddyTech

双参数分派

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

S4 泛型可以根据多个参数的类来选择方法。签名会为每个参数列出一个类,因此每组类都可以有自己的方法:

setClass("Asteroid", slots = c(size = "numeric"))
setClass("Ship", slots = c(name = "character"))
invisible(setGeneric("collide", function(a, b) standardGeneric("collide")))
setMethod("collide", signature("Asteroid", "Ship"), function(a, b) paste(b@name, "is hit"))
setMethod("collide", signature("Ship", "Ship"), function(a, b) "two ships bump")
rock <- new("Asteroid", size = 3)
enterprise <- new("Ship", name = "Enterprise")
print(collide(rock, enterprise))
print(collide(enterprise, enterprise))

输出:

[1] "Enterprise is hit"
[1] "two ships bump"

类 "ANY" 匹配所有类。对于 signature("ANY", "ANY") 的方法是回退方案,当没有更具体的配对适用时就会运行:

setClass("Ship", slots = c(name = "character"))
invisible(setGeneric("collide", function(a, b) standardGeneric("collide")))
setMethod("collide", signature("Ship", "Ship"), function(a, b) "two ships bump")
setMethod("collide", signature("ANY", "ANY"), function(a, b) "nothing happens")
s <- new("Ship", name = "A")
cat(collide(s, s), "/", collide(s, 42), "/", collide("x", "y"))

输出:

two ships bump / nothing happens / nothing happens

参数的顺序很重要:signature("A", "B") 和 signature("B", "A") 是不同的方法。签名中也可以使用 numeric 和 character 等基本类:

invisible(setGeneric("join", function(x, y) standardGeneric("join")))
setMethod("join", signature("character", "numeric"), function(x, y) paste(x, "number", y))
setMethod("join", signature("numeric", "character"), function(x, y) paste(y, "after number", x))
print(join("item", 3))
print(join(3, "item"))

输出:

[1] "item number 3"
[1] "item after number 3"

签名中的 "missing" 类会匹配完全没有提供的参数,因此一个泛型可以提供简短形式和完整形式:

invisible(setGeneric("greet", function(name, greeting) standardGeneric("greet")))
setMethod("greet", signature("character", "missing"), function(name, greeting) paste("Hello,", name))
setMethod("greet", signature("character", "character"), function(name, greeting) paste0(greeting, ", ", name))
print(greet("Ada"))
print(greet("Ada", "Good morning"))

输出:

[1] "Hello, Ada"
[1] "Good morning, Ada"
challenge icon

挑战

简单

运费取决于物品和区域。类在 Item.R(Letter、Box,每个类都有一个 kg 插槽)和 Zone.R(Domestic、Abroad,每个类都有一个 name 插槽)中声明。在 Rates.R 中创建泛型函数 shipping(item, zone),并为每一对类型创建一个方法:

  • Letter 在本地:1;在国外:3。
  • Box 在本地:5,每千克加 2;在国外:12,每千克加 4。
  • 其他任何组合:使用 no rate for this item and zone 停止,并使用 call. = FALSE。

提供的代码会读取多行输入,例如 box home 2.5(物品、区域、千克数),其中未知单词会保持为普通文本,并输出 box to home: 10.00 或 error: 和错误消息。

你的代码写在 Item.R、Zone.R 和 Rates.R 中。main.R 存放提供的输入/输出代码,不能编辑。

亲手试试

source("Item.R")
source("Zone.R")
source("Rates.R")

# 提供的输入/输出代码:保持原样
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
  p <- strsplit(line, " ")[[1]]
  kg <- as.numeric(p[3])
  item <- switch(p[1], letter = new("Letter", kg = kg), box = new("Box", kg = kg), kg)
  zone <- switch(p[2], home = new("Domestic", name = "home"), abroad = new("Abroad", name = "abroad"), p[2])
  out <- tryCatch(sprintf("%s to %s: %.2f", p[1], p[2], shipping(item, zone)),
                  error = function(e) paste("error:", conditionMessage(e)))
  cat(out, "\n", sep = "")
}
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: R 在线编译器