关于 R,有人可以向我解释一下,关于对象继承,如果我有 S4 对象 X,其中包含 Y,如果 Y 有一个初始化程序,当 X 是时,如何从 X 的初始化程序中调用该初始化程序建。
1 回答
第一次通过,不够好
这里有两个类
.A <- setClass("A", representation(a="integer"))
.B <- setClass("B", contains="A", representation(b="integer"))
该符号.A
是一个类生成器函数(本质上是对 的调用new()
),并且是方法包中相对较新的补充。
这里我们写了一个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
可以用作复制构造函数(请记住,没有 initialize,A1-method 或 initialize,B1-method,因此调用.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
使用a1
插槽的当前值.Object
作为默认值,当方法被用作复制构造函数时相关。该示例说明了使用 aprototype
来提供不同于 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
不幸的是,当尝试从基类初始化派生类时,这种方法失败了。
其他注意事项
最后一点是初始化方法本身的结构。上图是图案
## do class initialization steps, then...
callNextMethod(<...>)
callNextMethod()
在初始化方法的末尾也是如此。另一种选择是
.Object <- callNextMethod(<...>)
## do class initialization steps by modifying .Object, e.g.,...
.Object@a <- <...>
.Object
首选第一种方法的原因是涉及的复制较少;默认初始化,ANY 方法以最少的复制填充槽,而槽更新方法在每次修改槽时复制整个对象;如果对象包含大向量,这可能会非常糟糕。