这个问题几乎是一个哲学问题:使用@
直接访问和/或设置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
答案 0 :(得分:16)
在this question中,stackoverflow-er询问为什么他们无法在Bioconductor IRanges对象中找到end
个插槽;毕竟有start()
,width()
和end()
个访问者以及start
和width
个广告位。答案是因为用户与类的接口方式与实现方式不同。在这种情况下,实现是由简单的观察驱动的,即当只有两个(两个?直到开发人员!)就足够时,存储三个值(开始,结束,宽度)不节省空间。在其他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
- 包中,多边形存储方式的实现可能因速度或进展知识而发生变化。使用@
,您的代码会崩溃,使用您的代码仍可使用的界面。如果界面也发生变化,则除外。但是实现的更改比界面更改更有可能。