mcpField
R
r-oop

r-oop

github.com/ab604开发21058 次安装6 周前更新
代码仓库

关于 r-oop

帮助 R 开发者在 S7、S3、S4 和 vctrs 之间选择面向对象方案,并提供类、方法、继承及迁移模式。适用于设计 R 对象和评估现有代码迁移的场景。

主要功能

  1. 01比较 S7、S3、S4 与 vctrs
  2. 02按对象类型选择方案
  3. 03展示类与方法实现模式
  4. 04说明继承和多重分派
  5. 05提供系统迁移建议

适用场景

  1. 01为新项目选择对象系统
  2. 02构建向量型自定义类
  3. 03为复杂对象添加验证
  4. 04规划 S3 或 S4 迁移

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ab604/claude-code-r-skills --skill r-oop

安装后在 Claude Code 中输入/r-oop确认已加载。

详细介绍

R 面向对象编程

S7、S3、S4 和 vctrs:为你的需求选择合适的 OOP 系统

S7:适用于新项目的现代 OOP

  • S7 结合了 S3 的简洁性与 S4 的结构性
  • 提供带自动验证的正式类定义
  • 兼容现有 S3 代码
# S7 class definition
Range <- new_class("Range",
  properties = list(
    start = class_double,
    end = class_double
  ),
  validator = function(self) {
    if (self@end < self@start) {
      "@end must be >= @start"
    }
  }
)

# Usage - constructor and property access
x <- Range(start = 1, end = 10)
x@start  # 1
x@end <- 20  # automatic validation

# Methods
inside <- new_generic("inside", "x")
method(inside, Range) <- function(x, y) {
  y >= x@start & y <= x@end
}

OOP 系统决策矩阵

S7、vctrs 与 S3/S4 决策树

**从这里开始:**你要构建什么?

1. 类向量对象(行为类似原子向量的对象)
Use vctrs when:
- Need data frame integration (columns/rows)
- Want type-stable vector operations
- Building factor-like, date-like, or numeric-like classes
- Need consistent coercion/casting behavior
- Working with existing tidyverse infrastructure

Examples: custom date classes, units, categorical data
2. 通用对象(复杂数据结构,不像向量)
Use S7 when:
- NEW projects that need formal classes
- Want property validation and safe property access (@)
- Need multiple dispatch (beyond S3's double dispatch)
- Converting from S3 and want better structure
- Building class hierarchies with inheritance
- Want better error messages and discoverability

Use S3 when:
- Simple classes with minimal structure needs
- Maximum compatibility and minimal dependencies
- Quick prototyping or internal classes
- Contributing to existing S3-based ecosystems
- Performance is absolutely critical (minimal overhead)

Use S4 when:
- Working in Bioconductor ecosystem
- Need complex multiple inheritance (S7 doesn't support this)
- Existing S4 codebase that works well

S7 与 S3 详细对比

特性S3S7S7 的优势场景
类定义非正式(约定)正式(new_class())需要有保证的结构
属性访问$ 或 attr()(不安全)@(安全且经过验证)属性验证很重要
验证手动且不一致内置验证器数据完整性很重要
方法查找难以查找方法方法打印清晰开发者体验很重要
多重分派有限(基础泛型)完整的多重分派需要复杂的方法分派
继承非正式,NextMethod()显式的 super()需要可预测的继承
迁移成本-低(1-2 小时)希望获得更好的结构
性能最快与 S3 基本相同性能差异可以忽略
兼容性完全兼容 S3完全兼容 S3 和 S7需要兼顾新旧模式

实用指南

适合选择 S7 的情况
# Complex validation needs
Range <- new_class("Range",
  properties = list(start = class_double, end = class_double),
  validator = function(self) {
    if (self@end < self@start) "@end must be >= @start"
  }
)

# Multiple dispatch needs
method(generic, list(ClassA, ClassB)) <- function(x, y) ...

# Class hierarchies with clear inheritance
Child <- new_class("Child", parent = Parent)
需要以下功能时选择 vctrs
# Vector-like behavior in data frames
percent <- new_vctr(0.5, class = "percentage")
data.frame(x = 1:3, pct = percent(c(0.1, 0.2, 0.3)))  # works seamlessly

# Type-stable operations
vec_c(percent(0.1), percent(0.2))  # predictable behavior
vec_cast(0.5, percent())          # explicit, safe casting
适合选择 S3 的情况
# Simple classes without complex needs
new_simple <- function(x) structure(x, class = "simple")
print.simple <- function(x, ...) cat("Simple:", x)

# Maximum performance needs (rare)
# Existing S3 ecosystem contributions

S3 模式

基础 S3 类
# Constructor
new_person <- function(name, age) {
  stopifnot(is.character(name), length(name) == 1)
  stopifnot(is.numeric(age), length(age) == 1)

  structure(
    list(name = name, age = age),
    class = "person"
  )
}

# Print method
print.person <- function(x, ...) {
  cat("Person:", x$name, "(age", x$age, ")\n")
  invisible(x)
}

# Generic + method
greet <- function(x) UseMethod("greet")
greet.person <- function(x) {
  cat("Hello, my name is", x$name, "\n")
}
greet.default <- function(x) {
  cat("Hello!\n")
}
S3 继承
# Child class
new_employee <- function(name, age, company) {
  obj <- new_person(name, age)
  obj$company <- company
  class(obj) <- c("employee", class(obj))
  obj
}

# Method with inheritance
print.employee <- function(x, ...) {
  NextMethod()  # Call parent print method
  cat("Works at:", x$company, "\n")
  invisible(x)
}

S7 模式

基础 S7 类
library(S7)

# Define class
Person <- new_class("Person",
  properties = list(
    name = class_character,
    age = class_numeric
  ),
  validator = function(self) {
    if (self@age < 0) {
      "@age must be non-negative"
    }
  }
)

# Create instance
bob <- Person(name = "Bob", age = 30)
bob@name  # "Bob"
bob@age <- 31  # Validated assignment
S7 方法
# Define generic
greet <- new_generic("greet", "x")

# Add method
method(greet, Person) <- function(x) {
  cat("Hello, my name is", x@name, "\n")
}

# Default method
method(greet, class_any) <- function(x) {
  cat("Hello!\n")
}
S7 继承
Employee <- new_class("Employee",
  parent = Person,
  properties = list(
    company = class_character
  )
)

# Override method
method(greet, Employee) <- function(x) {
  super(x, Person)@greet()  # Call parent method
  cat("I work at", x@company, "\n")
}
S7 多重分派
# Generic with multiple dispatch
combine <- new_generic("combine", c("x", "y"))

# Method for specific combination
method(combine, list(Person, Person)) <- function(x, y) {
  cat(x@name, "meets", y@name, "\n")
}

method(combine, list(Person, class_character)) <- function(x, y) {
  cat(x@name, "receives message:", y, "\n")
}

迁移策略

  1. S3 -> S7:通常需要 1-2 小时,并保留完全兼容性
  2. S4 -> S7:更复杂,请评估是否确实需要 S4 的功能
  3. Base R -> vctrs:适用于类向量类,能带来显著优势
  4. 结合使用:S7 类可以在内部采用 vctrs 原则
迁移示例:从 S3 迁移到 S7
# Original S3
new_person_s3 <- function(name, age) {
  structure(list(name = name, age = age), class = "person")
}

# Migrated S7
Person <- new_class("Person",
  properties = list(
    name = class_character,
    age = class_numeric
  )
)

# S7 is backwards compatible with S3 generics
# Existing S3 methods still work

何时不使用 OOP

有时,更简单的方法更好:

# Don't create a class for simple data
# BAD
Point <- new_class("Point", properties = list(x = class_double, y = class_double))

# GOOD - just use a named list or vector
point <- c(x = 1.5, y = 2.3)

# Don't create classes for one-off operations
# Use functions instead
distance <- function(p1, p2) {
  sqrt((p1["x"] - p2["x"])^2 + (p1["y"] - p2["y"])^2)
}

评论

支持 Markdown