Menu
CoddyTech

虚拟类

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

虚拟类描述了一组类的共同点,并且不能拥有自己的对象。使用 representation("VIRTUAL") 声明它;它的子类照常使用 contains:

setClass("Shape", representation("VIRTUAL"))
setClass("Circle", contains = "Shape", slots = c(r = "numeric"))
setClass("Square", contains = "Shape", slots = c(side = "numeric"))
cat(isVirtualClass("Shape"), is(new("Circle", r = 1), "Shape"), "\n")
cat(tryCatch({ new("Shape"); "made" }, error = function(e) "not allowed"), "\n")

输出:

TRUE TRUE 
not allowed 

虚拟类的方法适用于所有子类。这里,Shape 的 describe() 会调用 area(),而每个子类都会实现该方法,因此描述只需编写一次:

setClass("Shape", representation("VIRTUAL"))
setClass("Circle", contains = "Shape", slots = c(r = "numeric"))
setClass("Square", contains = "Shape", slots = c(side = "numeric"))
invisible(setGeneric("area", function(s) standardGeneric("area")))
invisible(setGeneric("describe", function(s) standardGeneric("describe")))
setMethod("area", "Circle", function(s) pi * s@r^2)
setMethod("area", "Square", function(s) s@side^2)
setMethod("describe", "Shape", function(s) sprintf("%s with area %.2f", class(s), area(s)))
for (s in list(new("Circle", r = 1), new("Square", side = 2))) cat(describe(s), "\n")

输出:

Circle with area 3.14 
Square with area 4.00 

虚拟类可以声明槽,之后每个子类都会拥有这些槽:

setClass("Shape", representation("VIRTUAL", label = "character"))
setClass("Square", contains = "Shape", slots = c(side = "numeric"))
sq <- new("Square", label = "tile", side = 2)
print(slotNames("Square"))

输出:

[1] "side"  "label"

setClassUnion() 会根据现有类创建一个虚拟类。该类型的槽可以接受其中任意一种类型,例如联系人可以是电子邮件地址或电话号码:

setClassUnion("Contact", c("character", "numeric"))
setClass("Friend", slots = c(name = "character", contact = "Contact"))
a <- new("Friend", name = "Ada", contact = "ada@x.org")
b <- new("Friend", name = "Bo", contact = 5550123)
cat(class(a@contact), class(b@contact), "\n")
cat(tryCatch({ new("Friend", name = "Cy", contact = TRUE); "ok" }, error = function(e) "refused"), "\n")

输出:

character numeric 
refused 
challenge icon

挑战

简单

各种支付方式共用虚拟类 Payment(槽位 amount)和泛型 fee()、receipt(),它们都在 Payment.R 中。请完成以下内容:

  • 在 Payment.R 中,为 Payment 实现 receipt() 方法:card: 100.00 + 2.00 fee,由小写的类名、amount 和 fee() 组成;
  • 在 Card.R 中,实现类 Card(包含 Payment)及其 fee():金额的 2%;
  • 在 Cash.R 中,实现类 Cash 及其 fee():0。

提供的代码会读取 card 100 或 cash 20 这样的行,打印每张收据和收费总额,然后检查 Payment 是否为虚拟类,以及是否无法创建其实例。

你的代码应写在 Payment.R、Card.R 和 Cash.R 中。main.R 保存了提供的输入/输出代码,不能编辑。

亲手试试

source("Payment.R")
source("Card.R")
source("Cash.R")

# 已提供的输入输出代码:保持原样。
input <- suppressWarnings(readLines(file("stdin")))
total <- 0
for (line in input) {
  p <- strsplit(line, " ")[[1]]
  amount <- as.numeric(p[2])
  pay <- switch(p[1], card = new("Card", amount = amount), cash = new("Cash", amount = amount))
  cat(receipt(pay), "\n", sep = "")
  total <- total + amount + fee(pay)
}
cat(sprintf("charged: %.2f", total), "\n", sep = "")
cat("Payment is virtual: ", isVirtualClass("Payment"), "\n", sep = "")
cat(tryCatch({ new("Payment", amount = 1); "created a Payment" }, error = function(e) "cannot create a Payment"), "\n", sep = "")
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: R 在线编译器