6. Object-Oriented Programming
There are multiple ways to do object-oriented programming OOP in R. You will need to pick an object model from the following to start OOP.
S3is the object model for release 3S4is the object model for release 4R6is the current object model
6.1. S3
6.1.1. S3 class definition
Defining an S3 class is basically through using a function. Here, we have a class called S3Student which has the properties
name: characters
age: numeric
grades: vector
male: logical
Note the convention is to prefix S3 to the class name.
[1]:
S3Student <- function(name, age, grades, male) {
student <- list(name=name, age=age, grades=grades, male=male)
class(student) <- 'S3Student'
return(student)
}
Now we may instantiate a S3Student as follows.
[2]:
s <- S3Student('John', 18, c(99, 100, 80, 70, 90), TRUE)
[3]:
print(class(s))
[1] "S3Student"
[4]:
print(str(s))
List of 4
$ name : chr "John"
$ age : num 18
$ grades: num [1:5] 99 100 80 70 90
$ male : logi TRUE
- attr(*, "class")= chr "S3Student"
NULL
We may access the properties of the student using $ notation (since these properties are just stored in a named list).
[5]:
print(s$name)
[1] "John"
[6]:
print(s$age)
[1] 18
[7]:
print(s$grades)
[1] 99 100 80 70 90
[8]:
print(s$male)
[1] TRUE
6.1.2. S3 methods
Methods in S3 objects are not a part of the objects. Instead, we define them and then register the name with R to be able to use them properly.
generic functions: functions that are defined to be used over different typesparametric polymorphism: defining generic functions over types such that behavior will change depending on the type; methods belong to functions not classes
Below, we define a method S3grade that will compute the letter grade of a S3Student. We also register the method with R.
[9]:
# define S3grade function
S3grade.S3Student <- function(student) {
avg <- mean(student$grades)
if (avg >= 90.0) {
return('A')
} else if (avg >= 80.0) {
return('B')
} else if (avg >= 70.0) {
return('C')
} else if (avg >= 60.0) {
return('D')
} else {
return('F')
}
}
# register S3grade with R
S3grade <- function(object) UseMethod('S3grade')
Now we may use the S3grade function on a S3Student.
[10]:
grade <- S3grade(s)
print(grade)
[1] "B"
We can also overload the print function too.
[11]:
print.S3Student <- function(student) {
print(paste(
student$name, student$age, ifelse(student$male, 'male', 'female'), S3grade(student)
))
}
[12]:
print(s)
[1] "John 18 male B"
6.1.3. S3 inheritance
[13]:
S3Animal <- function(name, type='animal') {
animal <- list(name=name)
class(animal) <- 'S3Animal'
return(animal)
}
S3Dog <- function(name) {
dog <- S3Animal(name, 'dog')
class(dog) <- c('S3Dog', class(dog))
return(dog)
}
S3Cat <- function(name) {
cat <- S3Animal(name, 'cat')
class(cat) <- c('S3Cat', class(cat))
return(cat)
}
dog <- S3Dog('Clifford')
cat <- S3Cat('Heathcliff')
[14]:
print(class(dog))
[1] "S3Dog" "S3Animal"
[15]:
print(class(cat))
[1] "S3Cat" "S3Animal"
[16]:
S3sound.S3Animal <- function(object) return('argghhh!')
S3sound.S3Dog <- function(object) return('woof!')
S3sound.S3Cat <- function(object) return('meoww!')
S3sound <- function(object) UseMethod('S3sound')
[17]:
print(S3sound(dog))
[1] "woof!"
[18]:
print(S3sound(cat))
[1] "meoww!"
6.2. S4
6.2.1. S4 class definition
[19]:
setClass(
Class = 'S4Student',
representation = representation(
name = 'character',
age = 'numeric',
grades = 'numeric',
male = 'logical'
)
)
[20]:
s <- new('S4Student', name='John', age=18, grades=c(99, 100, 80, 70, 90), male=TRUE)
[21]:
print(class(s))
[1] "S4Student"
attr(,"package")
[1] ".GlobalEnv"
[22]:
print(str(s))
Formal class 'S4Student' [package ".GlobalEnv"] with 4 slots
..@ name : chr "John"
..@ age : num 18
..@ grades: num [1:5] 99 100 80 70 90
..@ male : logi TRUE
NULL
[23]:
print(s@name)
[1] "John"
[24]:
print(s@age)
[1] 18
[25]:
print(s@grades)
[1] 99 100 80 70 90
[26]:
print(s@male)
[1] TRUE
6.2.2. S4 methods
[27]:
setGeneric('S4grade', function(self) {
standardGeneric('S4grade')
})
setMethod('S4grade', 'S4Student', function(self) {
avg <- mean(self@grades)
if (avg >= 90.0) {
return('A')
} else if (avg >= 80.0) {
return('B')
} else if (avg >= 70.0) {
return('C')
} else if (avg >= 60.0) {
return('D')
} else {
return('F')
}
})
[28]:
grade <- S4grade(s)
print(grade)
[1] "B"
We do not overload print but provide a a method implementation for show so that we can use print.
[29]:
setMethod('show', 'S4Student', function(object) {
print(paste(
object@name, object@age, ifelse(object@male, 'male', 'female'), S4grade(object)
))
})
[30]:
print(s)
[1] "John 18 male B"
6.2.3. S4 inheritance
[31]:
setClass(
Class = 'S4Animal',
representation = representation(
name = 'character',
type = 'character'
)
)
setClass(Class='S4Dog', contains='S4Animal')
setClass(Class='S4Cat', contains='S4Animal')
dog = new('S4Dog', name='Clifford', type='dog')
cat = new('S4Cat', name='Heathcliff', type='cat')
[32]:
print(class(dog))
[1] "S4Dog"
attr(,"package")
[1] ".GlobalEnv"
[33]:
print(class(cat))
[1] "S4Cat"
attr(,"package")
[1] ".GlobalEnv"
[34]:
setGeneric('S4sound', function(self) {
standardGeneric('S4sound')
})
setMethod('S4sound', 'S4Animal', function(self) return('argghhh!'))
setMethod('S4sound', 'S4Dog', function(self) return('woof!'))
setMethod('S4sound', 'S4Cat', function(self) return('meoww!'))
[35]:
print(S4sound(dog))
[1] "woof!"
[36]:
print(S4sound(cat))
[1] "meoww!"
6.3. R6
6.3.1. R6 class definition
To define R6 classes, you will need to load the R6 library.
[37]:
library(R6)
[38]:
R6Student <- R6Class(
'R6Student',
public = list(
initialize = function(name, age, grades, male) {
private$name <- name
private$age <- age
private$grades <- grades
private$male <- male
},
grade = function() {
avg = mean(private$grades)
if (avg >= 90.0) {
return('A')
} else if (avg >= 80.0) {
return('B')
} else if (avg >= 70.0) {
return('C')
} else if (avg >= 60.0) {
return('D')
} else {
return('F')
}
},
print = function() {
print(paste(
private$name, private$age, ifelse(private$male, 'male', 'female'), self$grade()
))
invisible(self)
}
),
private = list(name=NULL, age=NULL, grades=NULL, male=NULL)
)
[39]:
s <- R6Student$new('John', 18, c(99, 100, 80, 70, 90), TRUE)
[40]:
print(class(s))
[1] "R6Student" "R6"
[41]:
print(str(s))
Classes 'R6Student', 'R6' <R6Student>
Public:
clone: function (deep = FALSE)
grade: function ()
initialize: function (name, age, grades, male)
print: function ()
Private:
age: 18
grades: 99 100 80 70 90
male: TRUE
name: John
NULL
[42]:
print(s)
[1] "John 18 male B"
6.3.2. R6 inheritance
Note the use of
active bindingtoaccessandmutatefields.Note the use of
finalizeto clean up resources.
[43]:
R6Animal <- R6Class(
'R6Animal',
public = list(
initialize = function(name, type='animal') {
private$name <- name
private$type <- type
},
sound = function() {
print('argghhh!')
invisible(self)
},
print = function() {
print(paste(
private$name, private$type
))
invisible(self)
},
finalize = function() {
print(paste('finalizer called', private$name, private$type))
}
),
private = list(name=NULL, type=NULL),
active = list(
name_field = function(new_name) {
if (missing(new_name)) {
return(private$name)
} else {
private$name <- new_name
}
},
sound_field = function(new_sound) {
if (missing(new_sound)) {
return(private$sound)
} else {
private$sound <- new_sound
}
}
)
)
R6Dog <- R6Class(
'R6Dog',
inherit=R6Animal,
public = list(
initialize = function(name) {
super$initialize(name, type='dog')
},
sound = function() {
print('woof!')
invisible(self)
}
)
)
R6Cat <- R6Class(
'R6Cat',
inherit=R6Animal,
public = list(
initialize = function(name) {
super$initialize(name, type='cat')
},
sound = function() {
print('meoww!')
invisible(self)
}
)
)
dog <- R6Dog$new('Clifford')
cat <- R6Cat$new('Heathcliff')
[44]:
print(class(dog))
[1] "R6Dog" "R6Animal" "R6"
[45]:
print(class(cat))
[1] "R6Cat" "R6Animal" "R6"
[46]:
dog$sound()
[1] "woof!"
[47]:
cat$sound()
[1] "meoww!"
You may mutate the name field as well.
[48]:
# original name
print(dog)
# mutate name
dog$name_field <- 'Droopy'
print(dog)
[1] "Clifford dog"
[1] "Droopy dog"
6.3.3. Introspection
To list all methods and fields, use names.
[49]:
print(names(dog))
[1] ".__enclos_env__" "sound_field" "name_field" "clone"
[5] "sound" "initialize" "finalize" "print"