21

这几乎是一个哲学问题:直接使用访问和/或设置 S4 对象的插槽是否不好@

我一直被告知这是不好的做法,用户应该使用“访问器”S4 方法,并且开发人员应该为他们的用户提供这些方法。但我想知道是否有人知道这背后的真正交易?

这是一个使用sp包的示例(但可以推广到任何 S4 类):

> library(sp)
> foo <- data.frame(x = runif(5), y = runif(5), bar = runif(5))
> coordinates(foo) <- ~x+y
> class(foo)
[1] "SpatialPointsDataFrame"
attr(,"package")
[1] "sp"

> str(foo)
Formal class 'SpatialPointsDataFrame' [package "sp"] with 5 slots
  ..@ data       :'data.frame': 5 obs. of  1 variable:
  .. ..$ bar: num [1:5] 0.621 0.273 0.446 0.174 0.278
  ..@ coords.nrs : int [1:2] 1 2
  ..@ coords     : num [1:5, 1:2] 0.885 0.763 0.591 0.709 0.925 ...
  .. ..- attr(*, "dimnames")=List of 2
  .. .. ..$ : NULL
  .. .. ..$ : chr [1:2] "x" "y"
  ..@ bbox       : num [1:2, 1:2] 0.591 0.155 0.925 0.803
  .. ..- attr(*, "dimnames")=List of 2
  .. .. ..$ : chr [1:2] "x" "y"
  .. .. ..$ : chr [1:2] "min" "max"
  ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slots
  .. .. ..@ projargs: chr NA

> foo@data
        bar
1 0.6213783
2 0.2725903
3 0.4458229
4 0.1743419
5 0.2779656
> foo@data <- data.frame(bar = letters[1:5], baz = runif(5))
> foo@data
  bar        baz
1   a 0.22877446
2   b 0.93206667
3   c 0.28169866
4   d 0.08616213
5   e 0.36713750
4

4 回答 4

18

这个问题中,stackoverflow-er 询问为什么他们在Bioconductor IRanges 对象中找不到end插槽;毕竟有, , 和访问器和和插槽。答案是因为用户与类交互的方式与实现方式不同。在这种情况下,实现是由一个简单的观察驱动的,即当只有两个(哪两个?取决于开发人员!)就足够了时,存储三个值(开始、结束、宽度)是不节省空间的。其他 S4 对象和常见的 S3 实例(如由start()width()end()startwidthlm,其中存储在类中的数据适用于后续计算,而不是针对特定用户可能最感兴趣的数量进行定制。如果您要访问该lm实例并更改值,则不会有任何好处,例如,coefficients元素。这种接口与实现的分离为开发人员提供了很大的自由度,可以提供合理且持续的用户体验,可能与其他类似的类共享,但以使编程有意义的方式实现(并更改实现)类。

我猜这并不能真正回答您的问题,但是开发人员不希望用户直接访问插槽,并且用户不应该期望直接访问插槽是与类交互的适当方式。

于 2012-03-28T03:39:17.593 回答
13

简而言之,开发人员应该为每个用例提供方法,但在实践中这非常困难,而且涵盖所有可能的用途很复杂。从技术上讲,就我而言,如果您需要的不仅仅是开发人员提供的,而且您必须使用“@”来获取未公开的功能,那么您就是开发人员(在 GNU 软件中,区别很模糊)。

这个sp包是一个很好的例子来问这个问题,因为“多边形”和“线”所需的分层数据结构的复杂性引发了一些非常简单的问题。这是一个:

多边形和线的coordinates()方法只返回每个对象的质心,虽然对于点它返回对象的每个“坐标”,但这是因为“点”是“一对一”的。一个对象,一个坐标,同样适用于 SpatialPoints 和 SpatialPointsDataFrame。对于 Line 和 Polygon、Lines 和 Polygons、SpatialLines 和 SpatialPolygons 或 SpatialLinesDataFrame 和 SpatialPolygonsDataFrame,情况并非如此。它们本质上由>两个坐标线轨道或>三个坐标多“环”组成。如何从每个多分支 SpatialPolygon 中获取每个多边形中每个顶点的坐标?除非您使用“@”深入研究开发人员结构,否则您不能。

没有提供这个是开发商的疏忽吗?不,优势远远超过任何特定用户事后所能看到的问题。一般来说,您可以深入研究这一事实是一个巨大的好处,但您会自动承担开发人员的责任,如果您选择分享您的努力而不将其包装在方法中,则可能会使情况变得更加困难。

于 2012-03-28T10:08:25.093 回答
12

作为 S4 课程的开发者,我的看法是:

如果您使用 @阅读插槽,则需要您自担风险(就像您在 R 中所做的几乎所有事情一样 - 请参阅下面的一些著名示例)。话虽如此,S4 类的插槽实际上是文档化接口的一部分

通过@我看到的访问的主要优点是速度:

> microbenchmark (accessor = wl (chondro), direct = chondro@wavelength)
Unit: nanoseconds
      expr    min       lq   median       uq    max
1 accessor 333431 341289.5 346784.5 366737.5 654219
2   direct    165    212.5    395.0    520.0   1440

(除了返回@wavelength导致差异的插槽之外,访问器函数还会进行有效性检查。我希望每个体面的公共访问器函数都能确保有效性)

我什至建议在时间紧迫的情况下使用对我的类的槽的读取访问(例如,如果访问同一对象的许多子集,则可能值得每次跳过检查未更改对象的有效性),并且在我的包的代码我主要直接读取插槽,以确保在函数开头和对象可能变得无效的函数结尾处的有效性。有人可能会争辩说,@<-不检查有效性的 (R) 设计决策在实践中确实会导致巨大的开销,因为在 S4 对象上工作的方法不能依赖于有效的对象,因此即使是纯读访问的方法也必须进行有效性检查。

如果您考虑对插槽进行访问,那么您应该真正知道自己在做什么。@<-不做任何有效性检查,官方写访问器应该这样做。而且,写访问器可能做的不仅仅是更新一个槽以保持对象的状态一致。

所以,如果你写到一个插槽,期待发现自己在地狱,不要抱怨。;-)

沿着这个哲学路线进一步思考:我的包在 GPL 下是公开的。我不仅允许您根据需要调整代码,而且我想鼓励您根据需要开发/调整代码。实际上,在 R 中这真的很容易——在普通的交互式 R 会话中,一切都已经存在,包括对插槽的访问。这与使 R 非常强大但允许诸如

> T <- FALSE
> `+` <- `-`
> pi <- 3
> pi + 2
[1] 1
于 2012-03-28T12:42:11.057 回答
10

一般来说,将对象的内容与界面分开是一种很好的编程习惯,请参阅这篇维基百科文章。这个想法是将接口与实现分开,这样实现可以发生很大变化,而不会影响与该代码接口的任何代码,例如您的脚本。因此,使用@会创建不太健壮的代码,这些代码不太可能在几年内工作。例如,在sp@mdsummer 提到的-package 中,如何存储多边形的实现可能会因为速度或知识的进步而改变。使用@,您的代码会崩溃,使用您的代码仍然可以工作的界面。当然,如果界面也发生了变化。但是实现的更改比接口更改更有可能。

于 2012-03-28T13:02:53.590 回答