Object-oriented programming is a programming paradigm based on the concept of “objects”, which may contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods.
A class defines the behavior of objects by describing their attributes and their relationship to other classes. The class is also used when selecting methods, functions that behave differently depending on the class of their input. Classes are usually organised in a hierarchy: if a method does not exist for a child, then the parent’s method is used instead; the child inherits behavior from the parent.
S3 implements a style of OO programming called generic-function OO. This is different from most programming languages, like Java, C++, and C#, which implement message-passing OO. S3 is a very casual system. It has no formal definition of classes.
S4 works similarly to S3, but is more formal. S4 has formal class definitions, which describe the representation and inheritance for each class, and has special helper functions for defining generics and methods.
2.12RC objects behave more like objects do in most other programming languages, e.g., Python, Ruby, Java, and C#. RC classes are best used for describing stateful objects, objects that change over time.
# setRefClass() generates new objects
Account <- setRefClass("Account")
Account$new()
## Reference class object of class "Account"
# how to create fields ?
Account <- setRefClass("Account",
fields = list(balance = "numeric"))
# access field using $
a <- Account$new(balance = 100)
a$balance
## [1] 100
# mutate field
a$balance <- 200
a$balance
## [1] 200
# RC objects are mutable
b <- a
b$balance
## [1] 200
a$balance <- 0
b$balance
## [1] 0
# For this reason, RC objects come with a copy() method that allow you to make a copy of the object
c <- a$copy()
c$balance
## [1] 0
a$balance <- 100
c$balance
## [1] 0
# Add behaviour to objects through methods
# The deep assignment arrow, <<-, never creates a variable in the current environment,
# but instead modifies an existing variable found by walking up the parent environments.
Account <- setRefClass("Account",
fields = list(balance = "numeric"),
methods = list(
withdraw = function(x) {
balance <<- balance - x
},
deposit = function(x) {
balance <<- balance + x
}
)
)
a <- Account$new(balance = 100)
a$deposit(100)
a$balance
## [1] 200
# contains is the name of the parent RC class to inherit behaviour from.
NoOverdraft <- setRefClass("NoOverdraft",
contains = "Account",
methods = list(
withdraw = function(x) {
if (balance < x) stop("Not enough money")
balance <<- balance - x
}
)
)
accountJohn <- NoOverdraft$new(balance = 100)
accountJohn$deposit(50)
accountJohn$balance
## [1] 150
accountJohn$withdraw(200)
All reference classes eventually inherit from envRefClass.
is(accountJohn, "refClass")
## [1] TRUE
pryr::otype(accountJohn)
## [1] "RC"
Method dispatch is very simple in RC because methods are associated with classes, not functions. When you call x$f(), R will look for a method f in the class of x, then in its parent, then its parent’s parent, and so on. From within a method, you can call the parent method directly with callSuper(...).
# contains is the name of the parent RC class to inherit behaviour from.
NoOverdraft <- setRefClass("NoOverdraft",
contains = "Account",
methods = list(
withdraw = function(x) {
if (balance < x) stop("Not enough money")
#balance <<- balance - x
callSuper(x)
}
)
)
accountJohn <- NoOverdraft$new(balance = 100)
accountJohn$deposit(50)
accountJohn$balance
## [1] 150