Search code examples
rreference-class

Constructor reference classes


I like to change the the folowing src in that way, that the initialize function looks like certain contructors. I would like to change the folowing example

    Part.initialize<-function(...){
  args<-list(...)
  if(all(class(args[[1]])==c("XMLInternalElementNode", "XMLInternalNode", "XMLAbstractNode"))){
    attr<-xmlAttrs(node)
    .self$var1 <- if(is.na(attr["var1"])) vector() else attr["var1"]
    .self$var2 <- if(is.na(attr["var2"])) character() else as.character(attr["var2"])
  }else{
    .self$var1 <- if(is.null(args[["var1"]])) vector() else args[["var1"]]
    .self$var2 <- if(is.null(args[["var2"]])) character() else as.character(args[["var2"]])
  }
  .self
}

Part<-setRefClass(Class = "Part"
                 ,fields = c(var1 = "ANY", var2 = "character")
                 ,methods = list(initialize=Part.initialize))

txt = "<doc> <part var2=\"abc\"/> </doc>"

tree <- xmlTreeParse(txt, useInternalNodes = TRUE)
node<-xpathSApply(tree, "//part")[[1]]
part <- Part$new(node)

to something like:

 Part.initialize<-function(XMLNode){
        do something
   }
   Part.initialize<-function(var1=c(),var2=character()){
        do something
   }

Besides how to handle default value for type ANY? Till now I use vector().


Solution

  • Do not write an initialize method, write a public constructor (.Part: a constructor to be used by your code only, not the user). The public constructor's job is to transform user arguments to a consistent form for class methods

    .Part<-setRefClass(Class = "Part"
                      ,fields = c(var1 = "ANY", var2 = "character"))
    

    Use setOldClass to enable class dispatch

    setOldClass(c("XMLInternalElementNode", "XMLInternalNode",
                  "XMLAbstractNode"))
    

    Write your public constructor as an S4 generic and methods

    setGeneric("Part", function(x, ...) standardGeneric("Part"))
    
    setMethod("Part", "missing", function(x, ...) {
        .Part()
    })
    
    setMethod("Part", "XMLInternalNode", function(x, ...) {
        attr<-xmlAttrs(x)
        var1 <- if (!is.na(attr["var1"])) attr["var1"] else vector()
        var2 <- if (!is.na(attr["var2"])) attr["var2"] else character()
        .Part(var1=var1, var2=var2, ...)
    })
    
    setMethod("Part", "ANY", function(x, var2, ...) {
        .Part(var1=x, var2=var2, ...)
    })
    

    Add a copy constructor if desired

    setMethod("Part", "Part", function(x, ...) x$copy())
    

    or if your own initialize method does something additional and conforms to the contract of the default initialize method (which acts as a copy constructor too) use

    setMethod("Part", "Part", function(x, ...) .Part(x, ...))
    

    Add any common code shared by constructors to the initialize method, being sure that your initialize method acts as a copy constructor and works when invoked without any arguments.

    Make sure that simple test cases work

    library(XML)
    Part()
    Part(TRUE, "var2")
    txt <- "<doc> <part var2=\"abc\"/> </doc>"
    node <- xmlTreeParse(txt, useInternalNodes = TRUE)[["//part"]]
    p1 <- Part(node)
    p2 <- Part(p1)
    p1$var2 <- "xyz"
    p2$var2            ## "abc"