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.
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:
exists = "skip"
(default) - This will decorate the object with all
fields/methods that don't already exist
exists = "error"
- This will throw an error and prevent the
object being decorated
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.
Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1996). Design Patterns: Elements of Reusable Software. Addison-Wesley Professional Computing Series (p. 395).
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