Most of what this package can do is inspired by the R-package R6. Unlike R6, aoos relies on the methods package and the S4 class system. It was not written to be superior with respect to efficiency. Use aoos if you want to use object oriented programming as a means to organise your source code.
All examples in the following are adapted from the R6-package.
There is one function you have to remember:
defineClass
: To define a class.Everything you define inside the class definition will be public, objects with a leading dot in the name will be private. This can be overwritten by the functions private
and public
. Every public member will inherit from class function. A “public field” is the get/set method for an object hidden behind a closure (see the class Accessor if you dislike this). An exception are objects which inherit from class ‘environment’ so basically all reference classes in R. Those you can access without get/set methods.
Here is a simple class definition:
library(aoos)
Person <- defineClass("Person", {
personName <- ""
init <- function(name) {
self$personName(name)
self$greet()
}
greet <- function() {
cat(paste0("Hello, my name is ", self$personName(), ".\n"))
}
})
The return value of defineClass
is the constructor function which is assigned to the name Person
. All arguments to the constructor will be passed on to init
if you defined it.
ann <- Person("Ann")
## Hello, my name is Ann.
ann
## Class: Person
## public member:
## greet
## init
## personName
## self
ann$personName()
## [1] "Ann"
ann$personName("not Ann")
ann$greet()
## Hello, my name is not Ann.
Inside the class definition you can use self
but you do not have to; It just might be more explicit. You can also use the super-assignment operator <<-
to replace private fields. The following illustrates the difference:
Person <- defineClass("Person", {
.personName <- "" # .personName is private
init <- function(name) {
self$.personName <- name # option 1
.personName <<- name # option 2
greet()
}
greet <- public(function() {
cat(paste0("Hello, my name is ", .personName, ".\n")) # before: personName()
})
})
What you have to keep in mind is, that a public field is always hidden behind a get/set method and needs to be treated as a function. Private fields are just R objects and follow copy-on-modify semantics.
aoos classes inherit from class ‘aoos’. For that class some S4-methods are defined you can use:
show
which prints the public members of an object to the consolesummary
which tries to give a clue how much memory is used by an objectFor every class you define with defineClass
the S4 method initialize
is set. Do not change it. You can also use the S4 constructor new
to create a new instance of your class.
new("Person", "Ann")
## Hello, my name is Ann.
## Class: Person
## public member:
## greet
## init
## personName
## self
A class defined by defineClass
can inherit from other aoos classes. The expression (expr
in defineClass
) is first evaluated for the parent and then for the child in the same environment. So you can override methods and fields.
Queue <- defineClass("Queue", {
.queue <- list()
init <- function(...) {
for (item in list(...)) self$add(item)
}
add <- function(x) {
.queue <<- c(.queue, list(x))
invisible(self)
}
remove <- function() {
if (queueIsEmpty()) return(NULL)
head <- .queue[[1]]
.queue <<- .queue[-1]
head
}
queueIsEmpty <- function() length(.queue) == 0
})
HistoryQueue <- defineClass("HistoryQueue", contains = "Queue", {
.head_idx <- 0
remove <- function() {
if ((length(.queue) - .head_idx) == 0) return(NULL)
self$.head_idx <- .head_idx + 1
.queue[[.head_idx]]
}
show <- function() {
cat("Next item is at index", .head_idx + 1, "\n")
for (i in seq_along(.queue)) {
cat(i, ": ", .queue[[i]], "\n", sep = "")
}
}
})
q <- Queue(5, 6, "foo")
q$remove()
## [1] 5
q
## Class: Queue
## public member:
## add
## init
## queueIsEmpty
## remove
## self
hq <- HistoryQueue(5, 6, "foo")
hq
## Class: HistoryQueue
## public member:
## add
## init
## queueIsEmpty
## remove
## self
## show
hq$show()
## Next item is at index 1
## 1: 5
## 2: 6
## 3: foo
hq$remove()
## [1] 5
hq$show()
## Next item is at index 2
## 1: 5
## 2: 6
## 3: foo
hq$remove()
## [1] 6
Since these classes use the S4 system you can use setMethod
to define additional methods like show
. Define S4-methods always outside the defineClass
function as expr
will be evaluated every time a new instance is created.
setMethod("show", "HistoryQueue", function(object) {
object$show()
})
## [1] "show"
hq
## Next item is at index 3
## 1: 5
## 2: 6
## 3: foo
This is a little tricky. expr
is evaluated in a new environment whenever you create a new instance. If one class inherits from another the same environment is used for the evaluation. Thus, objects of the parent can be replaced and the scoping of the parent is preserved; functions defined for the parent and the child share the same environment. The parent environment of an aoos class is the environment in which defineClass
(for the parent) is called. This will typically be a package namespace. If you define class B in package pkgB which inherits from a class A from package pkgA class B will have the same scoping as a class in pkgA. Whenever you want to use functions from pkgB in class B you have to define them as part of the class definition or refer to them with pkgB::"functionName"
or pkgB:::"functionName"
.