我正在构建一个包来处理最多有4种不同类型的数据。这些类型中的每一种都是矩阵,data.frame或树形式的合法类。根据处理数据的方式和其他实验因素,这些数据组件中的一些可能会丢失,但是能够将此信息存储为特殊类的实例并且具有识别不同组件的方法仍然非常有用。数据
方法1:
我已经尝试了一种看起来像嵌套树的增量继承结构,其中每种数据类型组合都有自己明确定义的类。对于未来的其他数据类型来说,这似乎很难扩展,并且对于新开发人员来说,学习所有类名也很困难,无论这些名称组织得井井有条。
方法2:
第二种方法是创建一个包含所有4种数据类型的插槽的“主类”。为了使缺失数据的实例的槽为NULL,似乎有必要首先在NULL
类和新数据类型类之间定义虚拟类联合,然后使用虚拟类联合作为主类中相关槽的预期类。下面是一个示例(假设已经定义了每个数据类型类):
################################################################################
# Use setClassUnion to define the unholy NULL-data union as a virtual class.
################################################################################
setClassUnion("dataClass1OrNULL", c("dataClass1", "NULL"))
setClassUnion("dataClass2OrNULL", c("dataClass2", "NULL"))
setClassUnion("dataClass3OrNULL", c("dataClass3", "NULL"))
setClassUnion("dataClass4OrNULL", c("dataClass4", "NULL"))
################################################################################
# Now define the master class with all 4 slots, and
# also the possibility of empty (NULL) slots and an explicity prototype for
# slots to be set to NULL if they are not provided at instantiation.
################################################################################
setClass(Class="theMasterClass",
representation=representation(
slot1="dataClass1OrNULL",
slot2="dataClass2OrNULL",
slot3="dataClass3OrNULL",
slot4="dataClass4OrNULL"),
prototype=prototype(slot1=NULL, slot2=NULL, slot3=NULL, slot4=NULL)
)
################################################################################
所以这个问题可以改为:
这些方法中是否有更有效和/或更灵活的替代方案?
此示例从对SO question about setting the default value of slot to NULL的回答进行了修改。这个问题的不同之处在于我有兴趣知道R中用于创建带槽的最佳选项,如果需要可以为空,尽管在所有其他非空情况下都需要特定的复杂类。
答案 0 :(得分:2)
在我看来......
它有点违背了采用正式类系统的目的,然后创建一个包含错误定义的槽('A'或NULL)的类。至少我会尝试使DataClass1具有类似“NULL”的默认值。举个简单的例子,这里的默认值是零长度数字向量。
setClass("DataClass1", representation=representation(x="numeric"))
DataClass1 <- function(x=numeric(), ...) {
new("DataClass1", x=x, ...)
}
然后
setClass("MasterClass1", representation=representation(dataClass1="DataClass1"))
MasterClass1 <- function(dataClass1=DataClass1(), ...) {
new("MasterClass1", dataClass1=dataClass1, ...)
}
这样做的一个好处是方法不必测试插槽中的实例是NULL还是'DataClass1'
setMethod(length, "DataClass1", function(x) length(x@x))
setMethod(length, "MasterClass1", function(x) length(x@dataClass1))
> length(MasterClass1())
[1] 0
> length(MasterClass1(DataClass1(1:5)))
[1] 5
回应你关于警告用户访问“空”插槽时的评论,并记住用户通常希望函数做某事而不是告诉他们他们做错了什么,我可能会返回空对象{{ 1}}准确地反映了对象的状态。也许DataClass1()
方法会提供一个概述,增强了插槽的状态 - DataClass1:none。如果MasterClass1代表一种协调几种不同分析的方式,这似乎是特别合适的,用户可能只做一些分析。
此方法(或方法2)的局限性在于您没有获得方法调度 - 您无法编写仅适用于具有非零的show
个实例的实例的方法长度,并被迫进行某种手动调度(例如,使用DataClass1
或if
)。这似乎是对开发人员的限制,但它也适用于用户 - 用户无法了解哪些操作特别适合具有非零长度DataClass1实例的MasterClass1实例。
当你说层次结构中的类名称会让你的用户感到困惑时,似乎这可能指向一个更基本的问题 - 你很难做出全面的表示数据类型;用户永远无法跟踪ClassWithMatrixDataFrameAndTree,因为它不代表他们查看数据的方式。这可能是一个缩小你的野心的机会,只能真正解决你正在调查的地区最突出的部分。或者也许是一个重新思考用户如何思考并与他们收集的数据进行交互的机会,以及使用界面(用户看到的)与实现的分离(您如何选择表示数据)类)由类系统提供,以更有效地封装用户可能做的事情。
将命名和数量的类放在一边,当你说“将来难以扩展其他数据类型”时,我想知道S4类的某些细微差别是否会绊倒你?简短的解决方案是避免编写自己的switch
方法,并依赖于构造函数来完成棘手的工作,
initialize
然后
setClass("A", representation(x="numeric"))
setClass("B", representation(y="numeric"), contains="A")
A <- function(x = numeric(), ...) new("A", x=x, ...)
B <- function(a = A(), y = numeric(), ...) new("B", a, y=y, ...)