【问题标题】:Inheritance in RR中的继承
【发布时间】:2013-04-27 02:09:43
【问题描述】:

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

【问题讨论】:

  • 您能否向我们展示您在使用new() 时遇到困难的代码?
  • 我使用 new() 没有任何困难,那不是我的问题。我的问题是如何从封装类的初始化程序中调用超类的初始化程序。
  • 当我阅读问题时,它是如何从超类的初始化程序中调用子类的初始化程序。我仍然认为代码会让你的困难更加清楚。我想“封装”的工作可能有歧义。我在 R 语言定义中找不到它,虽然这个词出现在参考索引中,但它不在 S4 对象或函数的上下文中。

标签: r inheritance s4


【解决方案1】:

第一次通过,还不够好

这里有两个类

.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 使用.Objecta1 槽的当前值作为默认值,当方法被用作复制构造函数时相关。该示例说明了使用 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 方法以最少的复制填充槽,而槽更新方法在每次修改槽时复制整个对象;如果对象包含大向量,这可能会非常糟糕。

【讨论】:

  • ?setClass 中读到:“[...]这些参数目前是允许的,但要么它们不太可能有用,要么存在首选的现代替代方案。[...] @ 987654352@:为此类中的插槽提供默认数据的对象。更灵活的方法是为initialize() [...]”编写一个方法”现在,据我了解,您建议不要使用@987654354 @ 而是使用 prototype 这样来保留复制结构?那为什么官方 R-docs 要弃用 prototype
  • @DavorJosipovic 是的,我建议反对initialize()。我无法回答“为什么官方 R 文档...”。
猜你喜欢
  • 1970-01-01
  • 2021-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-08
  • 1970-01-01
相关资源
最近更新 更多