我正在构建一个包来处理最多 4 种不同类型的数据。这些类型中的每一种都是矩阵、数据框或树形式的合法类。根据数据处理方式和其他实验因素,其中一些数据组件可能会丢失,但能够将此信息存储为特殊类的实例并具有识别不同组件的方法仍然非常有用数据。
方法一:
我尝试了一种增量继承结构,它看起来像嵌套树,其中数据类型的每种组合都有其自己的显式定义的类。这似乎很难在未来扩展更多的数据类型,并且对于新开发人员来说,学习所有类名称也具有挑战性,无论这些名称组织得多么好。
方法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)
)
################################################################################
Run Code Online (Sandbox Code Playgroud)
所以这个问题可以改写为:
这些方法是否有更有效和/或更灵活的替代方案?
此示例是根据有关将 slot 的默认值设置为 NULL 的 SO 问题的答案修改的。这个问题的不同之处在于,我有兴趣了解 R 中创建具有可以为空的插槽的类(如果需要)的最佳选项,尽管在所有其他非空情况下都需要特定的复杂类。
在我看来...
它有点违背了采用正式的类系统,然后创建一个包含不明确定义的槽(“A”或 NULL)的类的目的。至少我会尝试使 DataClass1 具有类似“NULL”的默认值。作为一个简单的例子,这里的默认值是零长度数值向量。
setClass("DataClass1", representation=representation(x="numeric"))
DataClass1 <- function(x=numeric(), ...) {
new("DataClass1", x=x, ...)
}
Run Code Online (Sandbox Code Playgroud)
然后
setClass("MasterClass1", representation=representation(dataClass1="DataClass1"))
MasterClass1 <- function(dataClass1=DataClass1(), ...) {
new("MasterClass1", dataClass1=dataClass1, ...)
}
Run Code Online (Sandbox Code Playgroud)
这样做的好处之一是方法不必测试槽中的实例是否为 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
Run Code Online (Sandbox Code Playgroud)
为了回应您关于在访问“空”插槽时警告用户的评论,并记住用户通常希望函数执行某些操作而不是告诉他们做错了什么,我可能会返回DataClass1()准确反映状态的空对象的对象。也许有一种show方法可以提供一个概述来强化槽的状态——DataClass1:无。如果 MasterClass1 代表一种协调几种不同分析的方法,而用户只能执行其中的一些分析,那么这似乎特别合适。
这种方法(或方法 2)的限制是您无法获得方法分派——您无法编写仅适用于具有DataClass1非零长度的实例的方法,并且被迫执行一些操作某种手动调度(例如,使用if或switch)。这对于开发人员来说似乎是一个限制,但它也适用于用户——用户不知道哪些操作特别适合具有非零长度 DataClass1 实例的 MasterClass1 实例。
当您说层次结构中的类名称会让您的用户感到困惑时,这似乎可能指向一个更基本的问题——您太努力地试图对数据类型进行全面的表示;用户永远无法跟踪 ClassWithMatrixDataFrameAndTree,因为它不代表他们查看数据的方式。这也许是一个缩小你的野心的机会,只真正解决你正在调查的领域中最突出的部分。或者也许有机会重新思考用户如何看待他们收集的数据并与之交互,并使用界面(用户所看到的)与实现(您选择如何在数据中表示数据)的分离。类)由类系统提供,以更有效地封装用户可能执行的操作。
抛开类的命名和数量不谈,当您说“将来很难扩展其他数据类型”时,我想知道 S4 类的一些细微差别是否会让您感到困惑?简短的解决方案是避免编写自己的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, ...)
Run Code Online (Sandbox Code Playgroud)
进而
> B(A(1:5), 10)
An object of class "B"
Slot "y":
[1] 10
Slot "x":
[1] 1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)