Creates a decorator R6 class by placing a thin wrapper around R6::R6Class which allows the constructed class to inherit the fields and methods of the given object.

Details

The decorator design pattern allows methods to be added to an object without bloating the interface with too many methods on construction and without causing large inheritance trees. A decorator class contains fields/methods that are 'added' to the given object in construction, this is made clearer in examples.

There are three possibilities when trying to decorate an object with a field/method that already exists:

  1. exists = "skip" (default) - This will decorate the object with all fields/methods that don't already exist

  2. exists = "error" - This will throw an error and prevent the object being decorated

  3. exists = "overwrite" - This will decorate the object with all fields/methods from the decorator and overwrite ones with the same name if they already exist

Decorators are currently not cloneable.

All arguments of R6::R6Class can be used as usual, see full details at R6::R6Class.

References

Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1996). Design Patterns: Elements of Reusable Software. Addison-Wesley Professional Computing Series (p. 395).

See also

Examples

library(R6)

## Create two decorators
# Works with active bindings...
dec1 <- DecoratorClass("dec1", active = list(hi = function() "Hi World"))
# And public fields...
dec2 <- DecoratorClass("dec2", public = list(goodbye = "Goodbye World"))

## Create an object to decorate
oop <- ooplah$new()
oop$hello()
#> [1] "Hello World, Ooplah!"


## Decorate with dec1 by constructing dec1 with object oop:
dec_oop <- dec1$new(oop) # equiv `decorate(oop, dec1)`
## We have all original methods from oop
dec_oop$hello()
#> [1] "Hello World, Ooplah!"
# It's inherited methods
dec_oop$init
#> [1] TRUE
# And now decorated methods
dec_oop$hi
#> [1] "Hi World"

## We can decorate again
redec_oop <- dec2$new(dec_oop)
redec_oop$hello()
#> [1] "Hello World, Ooplah!"
redec_oop$init
#> [1] TRUE
redec_oop$hi
#> [1] "Hi World"
# And now
redec_oop$goodbye
#> [1] "Goodbye World"

# Notice the class reflects all decorators, the original object and parents,
#  and adds the 'Decorator' class
class(redec_oop)
#> [1] "dec2"         "dec1"         "ooplah"       "OoplahParent" "Decorator"   
#> [6] "R6"          

## Decorators also work with inheritance
parent_dec <- DecoratorClass("parent_dec",
  public = list(hi = function() "Hi!"))
child_dec <- DecoratorClass("child_dec", inherit = parent_dec)
dec_oop <- child_dec$new(ooplah$new())
dec_oop$hi()
#> [1] "Hi!"

## Three possibilities if the method/field name already exists:
oop <- ooplah$new()
exists_dec <- DecoratorClass("exists_dec",
  public = list(hello = function() "Hi!"))

# 1. skip (default)
oop$hello()
#> [1] "Hello World, Ooplah!"
exists_dec$new(oop, exists = "skip")$hello()
#> [1] "Hello World, Ooplah!"

# 2. error
if (FALSE) {
exists_dec$new(oop)
exists_dec$new(oop, exists = "error")
}

# 3. overwrite
oop$hello()
#> [1] "Hello World, Ooplah!"
exists_dec$new(oop, exists = "overwrite")$hello()
#> [1] "Hi!"

## Cloning
# Note that by default the decorated object is not cloned
dec <- DecoratorClass("dec", active = list(hi = function() "Hi World"))
dec_oop <- dec$new(oop)
dec_oop$logically
#> [1] TRUE
oop$logically <- FALSE
dec_oop$logically
#> [1] FALSE