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.
S3
is the object model for release 3S4
is the object model for release 4R6
is 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 binding
toaccess
andmutate
fields.Note the use of
finalize
to 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"