Implementation of standard software design patterns (focus on MVC) in R

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)) { # Call method updateView() for each subscriber reference sapply(temp$subscribers, function(x) { x$updateView() }) } return(TRUE) } ) ) 

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" # Field value automatically set when initializing object. # See 'initialize()' method of class 'Model'. > mod$state [1] "6d95a520d4e3416bac93fbae88dfe02f" # Field value automatically set when initializing object. # See 'initialize()' method of class 'Model'. > ls(mod$observer$.X) [1] "fdf47649f4c25d99efe5d061b1655193" > get(mod$uid, mod$observer$.X) $state [1] "6d95a520d4e3416bac93fbae88dfe02f" 

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"?
+54
pass-by-reference design-patterns r reference-class
Mar 12 2018-12-12T00:
source share
1 answer
  • This looks good, but I'm not sure why you have an Observer for other classes (maybe you can tell me). Usually the controller is an observer. It is a really good idea to do this in R because when I found out about it in Java it wasn’t easy to understand (Java hides some of the good parts)

  • Yes and no. There are many different interpretations of this pattern. I like to have methods in the object, I would say that it belongs to the model. A simple example would be a sudoku solution that shows the solution steps in a graphical interface. Let me divide it into several parts, which can be divided into M, V and C: raw data (possibly a 2D array), sudoku functions (output the next step, ...), a graphical interface, the one who tells the GUI that it’s new the step was calculated I would say this: M: raw data + sudoku functions, C: who tells the GUI about the changes / models about the GUI inputs, V: GUI without any logic, others put the sudoku function into the controller, they are also right and can work better for some problems.

  • It’s possible to have a “one way” controller, as you call it, and View is the observer of the model. You can also let the controller do everything, and Model and View do not know each other (look at Model View Presenter, what about this)

+2
Jul 27 '12 at 17:43
source share



All Articles