Читать книгу The Big R-Book - Philippe J. S. De Brouwer - Страница 190
6.2.1 Creating S3 Objects
ОглавлениеS3 is a minimalistic and informal OO system; there is not even a formal definition of a class. In S3, you never create a class definition and start from the instance itself. Actually, to create an S3 object, it is sufficient to set its class attribute.
S3
my_curr_acc <- list(“name” = “Philippe”, “balance” <- 100) class(my_curr_acc) <- “account” # set the class attribute otype(my_curr_acc) # it is an S3 object ## [1] “S3” class(my_curr_acc) # the class type is defined above ## [1] “account” # Note that the class attribute is not visible in the structure: str(my_curr_acc) ## List of 2 ## $ name: chr “Philippe” ## $ : num 100 ## - attr(*, “class”)= chr “account”
It is also possible to create a class and set the attribute simultaneously with the function structure
.
structure()
my_object <- structure(list(), class = “boringClass”)
To create methods for S3 generic function, all we have to do is follow the syntax: <<generic function>>.<<class name>>
. R will then make sure that if an object of “class name” calls the generic function that then the generic function will dispatch the action to this specific function.
generic function
# print.account # Print an object of type ‘account’ # Arguments: # x -- an object of type account print.account <- function(x){ print(paste(“account holder”,x[[1]],sep=”: “)) print(paste(“balance “ ,x[[2]],sep=”: “)) } print(my_curr_acc) ## [1] “account holder: Philippe” ## [1] “balance : 100”
S3 objects are always build on other more elementary types. The function inherits (x, “classname”)
allows the user to determine if the class x inherits from the class “classname.”
You probably remember that R returned “internal” “generic”
as the class for some functions; so the class can be a vector. That means that the behaviour of that object can depend on different class-specific methods. The classes have to be listed from from most to least specific, so that the behaviour can follow this cascade and will always execute the most specific behaviour if it is present.
For example, the class of the glm()
object is c(“glm”, “lm”)
. This means that the most specific class is the generalised linear model, but that some behaviour they might inherit from linear models. When a generic function will be called, it will first try to find a glm-specific method. If that fails, it will look for the lm-method.
It is possible to provide a constructor function for an S3 class. This constructor function can, for example be used to check if we use the right data-type for its attributes.
constructor
# account # Constructor function for an object of type account # Arguments: # x -- character (the name of the account holder) # y -- numeric (the initial balance of the account # Returns: # Error message in console in case of failure. account <- function(x,y) { if (!is.numeric(y)) stop(“Balance must be numeric!”) if (!is.atomic(x)) stop(“Name must be atomic!!”) if (!is.character(x)) stop(“Name must be a string!”) structure(list(“name” = x, “balance” = y), class = “account”) } # create a new instance for Paul: paul_account <- account(“Paul”, 200) # print the object with print.account(): paul_account ## [1] “account holder: Paul” ## [1] “balance : 200”
The advantage of using the creator function for an instance of an object is obvious: it will perform some checks and will avoid problems later on. Unlike in message-passing OO implementations, the S3 implementation allows to bypass the creator function or worse it allows you to change the class all too easy. Consider the following example.
creator function
class(paul_account) <- “data.frame” print(paul_account) # R thinks now it is a data.frame ## [1] name balance ## <0 rows> (or 0-length row.names) paul_account[[2]] # the data is still correct ## [1] 200 class(paul_account) <- “account” print(paul_account) # back to normal: the class is just an attribute ## [1] “account holder: Paul” ## [1] “balance : 200”