第一次通过,还不够好
这里有两个类
.A <- setClass("A", representation(a="integer"))
.B <- setClass("B", contains="A", representation(b="integer"))
符号.A 是一个类生成器函数(本质上是对new() 的调用),是methods 包中相对较新的补充。
这里我们写了一个initialize,A-method,使用callNextMethod为类调用下一个方法(默认构造函数initialize,ANY-method)
setMethod("initialize", "A", function(.Object, ..., a=integer()) {
## do work of initialization
cat("A\n")
callNextMethod(.Object, ..., a=a)
})
槽a=a对应的参数出现在...之后,因此函数不会将任何未命名的参数分配给a;这很重要,因为未命名的参数应该(来自?initialize)用于初始化基类,而不是槽;这一点的重要性在下面变得显而易见。对于“B”也是如此:
setMethod("initialize", "B", function(.Object, ..., b=integer()) {
cat("B\n")
callNextMethod(.Object, ..., b=b)
})
在行动中
> b <- .B(a=1:5, b=5:1)
B
A
> b
An object of class "B"
Slot "b":
[1] 5 4 3 2 1
Slot "a":
[1] 1 2 3 4 5
其实这不太正确,因为默认的initialize是一个拷贝构造函数
.C <- setClass("C", representation(c1="numeric", c2="numeric"))
c <- .C(c1=1:5, c2=5:1)
> initialize(c, c1=5:1)
An object of class "C"
Slot "c1":
[1] 5 4 3 2 1
Slot "c2":
[1] 5 4 3 2 1
而我们的初始化方法破坏了契约的这一方面
> initialize(b, a=1:5) # BAD: no copy construction
B
A
An object of class "B"
Slot "b":
integer(0)
Slot "a":
[1] 1 2 3 4 5
复制构造非常方便,所以我们不想破坏它。
保留复制构造
有两种解决方案可用于保留复制构造功能。第一个避免定义初始化方法,而是创建一个普通的旧函数作为构造函数
.A1 <- setClass("A1", representation(a="integer"))
.B1 <- setClass("B1", contains="A1", representation(b="integer"))
A1 <- function(a = integer(), ...) {
.A1(a=a, ...)
}
B1 <- function(a=integer(), b=integer(), ...) {
.B1(A1(a), b=b, ...)
}
这些函数包含... 作为参数,因此可以扩展类“B1”并且仍然使用其构造函数。这实际上很有吸引力;构造函数可以具有带有文档参数的合理签名。 initialize 可以用作复制构造函数(请记住,没有初始化,A1 方法或初始化,B1 方法,因此调用 .A1() 调用默认的、可复制构造函数的初始化方法)。函数 (.B1(A1(a), b=b, ...) 表示“调用 B1 类的生成器,使用 "A1" 构造函数创建其超类的未命名参数,以及对应于插槽 b 的命名参数”。如上所述,来自 ?initialize,未命名参数用于初始化超类(当类结构涉及多重继承时使用多个类)。使用构造函数意味着类 A1 和 B1 可以不知道彼此的结构和实现。
第二种不太常用的解决方案是编写一个保留复制结构的初始化方法,类似于
.A2 <- setClass("A2", representation(a1="integer", a2="integer"),
prototype=prototype(a1=1:5, a2=5:1))
setMethod("initialize", "A2",
function(.Object, ..., a1=.Object@a1, a2=.Object@a2)
{
callNextMethod(.Object, ..., a1=a1, a2=a2)
})
参数a1=.Object@a1 使用.Object 的a1 槽的当前值作为默认值,当方法被用作复制构造函数时相关。该示例说明了使用 prototype 来提供不同于 0 长度向量的初始值。实际操作:
> a <- .A2(a2=1:3)
> a
An object of class "A1"
Slot "a1":
[1] 1 2 3 4 5
Slot "a2":
[1] 1 2 3
> initialize(a, a1=-(1:3)) # GOOD: copy constructor
An object of class "A1"
Slot "a1":
[1] -1 -2 -3
Slot "a2":
[1] 1 2 3
不幸的是 this approach fails 在尝试从基类初始化派生类时。
其他注意事项
最后一点是初始化方法本身的结构。上图就是模式
## do class initialization steps, then...
callNextMethod(<...>)
所以callNextMethod() 在初始化方法的末尾。另一种选择是
.Object <- callNextMethod(<...>)
## do class initialization steps by modifying .Object, e.g.,...
.Object@a <- <...>
.Object
首选第一种方法的原因是涉及的复制较少;默认初始化,ANY 方法以最少的复制填充槽,而槽更新方法在每次修改槽时复制整个对象;如果对象包含大向量,这可能会非常糟糕。