15

关于 R,有人可以向我解释一下,关于对象继承,如果我有 S4 对象 X,其中包含 Y,如果 Y 有一个初始化程序,当 X 是时,如何从 X 的初始化程序中调用该初始化程序建。

4

1 回答 1

23

第一次通过,不够好

这里有两个类

.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 方法以最少的复制填充槽,而槽更新方法在每次修改槽时复制整个对象;如果对象包含大向量,这可能会非常糟糕。

于 2013-04-27T05:41:31.430 回答