Currently I read a lot about software development, software development, design templates, etc. Starting from a completely different background, all new and exciting things are for me, so please bear with me if I do not use the correct technical terminology to describe certain aspects; -)
In most cases, I used Reference Classes (the OOP method in R), since object orientation seems to be the right choice for a lot of what I'm doing.
Now I was wondering if anyone had any good advice or any experience with implementing MVC (Model View Controller, also known as MVP : Model View Presenter) in R, preferably using reference classes.
I also really liked the information about other "standard" design patterns, such as observer , blackboard , etc., but I don't want to ask too broad a question. I think the coolest thing will be to see a minimal sample code, but any pointer, “diagram”, diagram or any other idea will also be appreciated!
For those interested in such material, I really recommend the following books:
UPDATE 2012-03-12
In the end, I came up with a small example of my interpretation of MVC (which may not be entirely correct ;-)).
Package dependencies
require("digest")
Class Definition Browser
setRefClass( "Observer", fields=list( .X="environment" ), methods=list( notify=function(uid, ...) { message(paste("Notifying subscribers of model uid: ", uid, sep="")) temp <- get(uid, .self$.X) if (length(temp$subscribers)) {
Class Definition Model
setRefClass( "Model", fields=list( .X="data.frame", state="character", uid="character", observer="Observer" ), methods=list( initialize=function(...) { # Make sure all inputs are used ('...') .self <- callSuper(...) # Ensure uid .self$uid <- digest(c(.self, Sys.time())) # Ensure hash key of initial state .self$state <- digest(.self$.X) # Register uid in observer assign(.self$uid, list(state=.self$state), .self$observer$.X) .self }, multiply=function(x, ...) { .self$.X <- .X * x # Handle state change statechangeDetect() return(TRUE) }, publish=function(...) { message(paste("Publishing state change for model uid: ", .self$uid, sep="")) # Publish current state to observer if (!exists(.self$uid, .self$observer$.X)) { assign(.self$uid, list(state=.self$state), .self$observer$.X) } else { temp <- get(.self$uid, envir=.self$observer$.X) temp$state <- .self$state assign(.self$uid, temp, .self$observer$.X) } # Make observer notify all subscribers .self$observer$notify(uid=.self$uid) return(TRUE) }, statechangeDetect=function(...) { out <- TRUE # Hash key of current state state <- digest(.self$.X) if (length(.self$state)) { out <- .self$state != state if (out) { # Update state if it has changed .self$state <- state } } if (out) { message(paste("State change detected for model uid: ", .self$uid, sep="")) # Publish state change to observer .self$publish() } return(out) } ) )
Class Definition and View Controller
setRefClass( "Controller", fields=list( model="Model", views="list" ), methods=list( multiply=function(x, ...) { # Call respective method of model .self$model$multiply(x) }, subscribe=function(...) { uid <- .self$model$uid envir <- .self$model$observer$.X temp <- get(uid, envir) # Add itself to subscribers of underlying model temp$subscribers <- c(temp$subscribers, .self) assign(uid, temp, envir) }, updateView=function(...) { # Call display method of each registered view sapply(.self$views, function(x) { x$display(.self$model) }) return(TRUE) } ) ) setRefClass( "View1", methods=list( display=function(model, x=1, y=2, ...) { plot(x=model$.X[,x], y=model$.X[,y]) } ) ) setRefClass( "View2", methods=list( display=function(model, ...) { print(model$.X) } ) )
Defining a class for representing dummy data
setRefClass( "MyData", fields=list( .X="data.frame" ), methods=list( modelMake=function(...){ new("Model", .X=.self$.X) } ) )
Create Instances
x <- new("MyData", .X=data.frame(a=1:3, b=10:12))
To study the characteristics of the model and the state of the observer
mod <- x$modelMake() mod$.X > mod$uid [1] "fdf47649f4c25d99efe5d061b1655193"
Note that the uid object is automatically registered with the observer during initialization. This way, the controllers / views can subscribe to notifications, and we have a 1: n ratio.
Creating Views and Controller
view1 <- new("View1") view2 <- new("View2") cont <- new("Controller", model=mod, views=list(view1, view2))
Subscription
The controller subscribes to notifications of the base model
cont$subscribe()
Please note that the subscription has been registered with the observer.
get(mod$uid, mod$observer$.X)
Show registered views
> cont$updateView() ab 1 1 10 2 2 11 3 3 12 [1] TRUE
A chart window also opens.
Change Model
> cont$model$multiply(x=10) State change detected for model uid: fdf47649f4c25d99efe5d061b1655193 Publishing state change for model uid: fdf47649f4c25d99efe5d061b1655193 Notifying subscribers of model uid: fdf47649f4c25d99efe5d061b1655193 ab 1 10 100 2 20 110 3 30 120 [1] TRUE
Please note that both registered views are automatically updated, because the base model published its state change to the observer, who, in turn, notified all subscribers (i.e. the controller).
Open questions
Here I feel that I have not understood yet:
- Is this a somewhat correct implementation of the MVC pattern? If not, what have I done wrong?
- Should the "processing" methods (for example, aggregate data, accept subsets, etc.) for the model "belong" to the model or controller class. Until now, I have always defined everything that a particular object can “do” as methods of this object itself.
- If the controller is a kind of "proxy" that controls every interaction between the model and the views (sort of like "in both directions"), or is it only responsible for distributing user input to the model (like "one way"?