Object-Oriented Programming
R has several OOP systems:
informal, generic-function dispatch most of base R & CRAN (print, summary, lm) formal classes, multiple dispatch Bioconductor, Matrix reference classes, mutable encapsulated, mutable, fast Shiny, many packages new formal system (successor to S3/S4)
32.1 S3
Operators as generics: define "+.money" <- function(e1, e2) ..., "==.money", "[.myclass", "$.myclass", length.myclass, as.character.myclass.
Best practice (Advanced R): write a constructor (new_x), a validator (validate_x), and a user-facing helper (x()).
# Constructor
new_dog <- function(name, age) {
stopifnot(is.character(name), is.numeric(age))
structure(list(name = name, age = age), class = c("dog", "animal"))
}
# Generic
speak <- function(x, ...) UseMethod("speak")
# Methods
speak.dog <- function(x, ...) cat(x$name, "says Woof!\n")
speak.animal <- function(x, ...) cat("Some generic animal sound\n")
speak.default <- function(x, ...) stop("Don't know how to speak")
print.dog <- function(x, ...) { cat("<dog:", x$name, ">\n"); invisible(x) }
summary.dog <- function(object, ...) cat("Dog aged", object$age, "\n")
format.dog <- function(x, ...) paste0("Dog(", x$name, ")")
d <- new_dog("Rex", 3)
speak(d); d; summary(d)
inherits(d, "animal") # TRUE
methods(speak); methods(class = "dog")
NextMethod() # call the next method in the class chain (inside a method)
unclass(d)32.2 S4
Multiple dispatch: setMethod("combine", signature("Matrix", "numeric"), ...).
setClass("Person", representation(name = "character", age = "numeric"),
validity = function(object) if (object@age < 0) "age must be >= 0" else TRUE)
setGeneric("greet", function(obj, ...) standardGeneric("greet"))
setMethod("greet", "Person", function(obj, ...) cat("Hello,", obj@name, "\n"))
setMethod("show", "Person", function(object) cat("<Person:", object@name, ">\n"))
setClass("Student", contains = "Person", representation(school = "character"))
setMethod("greet", "Student", function(obj, ...) { callNextMethod(); cat("I study at", obj@school, "\n") })
p <- new("Student", name = "Ann", age = 20, school = "MIT")
greet(p); p@name; slot(p, "age"); isVirtualClass("Person"); is(p, "Person")
validObject(p); showMethods("greet"); existsMethod("greet", "Person")32.3 R6 — mutable, encapsulated objects
library(R6)
Account <- R6Class("Account",
public = list(
owner = NULL,
initialize = function(owner, balance = 0) {
self$owner <- owner
private$balance <- balance
},
deposit = function(x) { private$balance <- private$balance + x; invisible(self) },
withdraw = function(x) {
if (x > private$balance) stop("Insufficient funds")
private$balance <- private$balance - x
invisible(self)
},
print = function(...) { cat("Account of", self$owner, ":", private$balance, "\n"); invisible(self) }
),
private = list(balance = 0),
active = list(
current = function(value) { if (missing(value)) private$balance else stop("read-only") }
)
)
acc <- Account$new("Ann", 100)
acc$deposit(50)$withdraw(30) # method chaining
acc$current # 120
acc
Savings <- R6Class("Savings", inherit = Account,
public = list(add_interest = function(rate) self$deposit(self$current * rate)))
acc2 <- acc$clone() # R6 objects are references — clone to copy32.4 S7 — the modern successor
library(S7)
Dog <- new_class("Dog", properties = list(name = class_character, age = class_numeric),
validator = function(self) if (self@age < 0) "@age must be non-negative")
speak <- new_generic("speak", "x")
method(speak, Dog) <- function(x) cat(x@name, "says Woof\n")
rex <- Dog(name = "Rex", age = 3)
speak(rex); rex@age <- 432.5 Reference classes (RC)
Person <- setRefClass("Person", fields = list(name = "character"),
methods = list(greet = function() cat("Hi,", name, "\n")))
Person$new(name = "Ann")$greet()32.6 Choosing
S3 for most things (simple, idiomatic); R6 when you need mutable state (connections, simulations, Shiny modules); S4/S7 for large, formal class hierarchies or multiple dispatch.