使用@直接访问S4对象插槽是不好的做法?

时间:2012-03-28 01:51:46

标签: r s4

这个问题几乎是一个哲学问题:使用@直接访问和/或设置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 个答案:

答案 0 :(得分:16)

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

我猜这并没有真正回答你的问题,但开发人员并不期望用户直接访问插槽,用户不应期望直接插槽访问是一种合适的交互方式与班级。

答案 1 :(得分:12)

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

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

多边形和线条的coordinates()方法仅返回每个对象的质心,但对于点,它返回来自对象的每个“坐标”,但这是因为“点”是“一对一” 。一个对象,一个坐标,也适用于SpatialPoints和SpatialPointsDataFrame。对于线和多边形,或线和多边形,或SpatialLines和SpatialPolygons,或SpatialLinesDataFrame和SpatialPolygonsDataFrame,情况并非如此。它们固有地由>两个坐标线轨道或>三个坐标多边形“环”组成。如何从每个多分支SpatialPolygon中获取每个Polygon中每个顶点的坐标?除非你用“@”钻研开发者结构,否则你不能。

开发商没有提供这个吗?不,优点远远超过任何特定用户在事后看到的问题。一般来说,你可以钻研的事实是一个巨大的奖金,但你自动承担了开发人员的责任,如果你选择分享你的努力而不将其包装在方法中,可能会使情况更难。

答案 2 :(得分:10)

作为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槽之外还进行了valitidy检查。我希望每个体面的公共访问器功能都能确保有效性。

我甚至建议在时间紧迫的情况下使用对类的插槽的读访问(例如,如果访问同一对象的大量子集,则每次跳过检查未更改对象的有效性可能是值得的)在我的包的代码中,我主要直接读取插槽,确保在函数开始时和在对象可能变为无效的函数结束时的有效性。有人可能认为@<-不检查有效性的(R)设计决策确实会导致实践中的巨大开销,因为处理S4对象的方法不能依赖于对象有效,因此即使是具有纯读取访问权限的方法也是如此做有效性检查。

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

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

在哲学思路上进一步思考:我的包在GPL下是公开的。我不仅允许您根据需要调整代码,还希望鼓励您根据需要开发/调整代码。实际上它在R中非常简单 - 在普通的交互式R会话中已经存在一切,包括访问插槽。这与设计决策完全一致,使R非常强大,但允许像

这样的东西
> T <- FALSE
> `+` <- `-`
> pi <- 3
> pi + 2
[1] 1

答案 3 :(得分:7)

一般来说,将对象的内容与界面分开是一种很好的编程习惯,请参阅this wikipedia article。想法是使接口与实现分离,这样实现可以显着改变,而不会影响与该代码接口的任何代码,例如,你的脚本。因此,使用@会创建不太健壮的代码,这些代码在几年内不太可能正常工作。例如,在@mdsummer提到的sp - 包中,多边形存储方式的实现可能因速度或进展知识而发生变化。使用@,您的代码会崩溃,使用您的代码仍可使用的界面。如果界面也发生变化,则除外。但是实现的更改比界面更改更有可能。