Note that there are some explanatory texts on larger screens.

plurals
  1. POImplementing standard software design patterns (focus on MVC) in R
    primarykey
    data
    text
    <p>Currently, I'm reading a lot about Software Engineering, Software Design, Design Patterns etc. Coming from a totally different background, that's all new fascinating stuff to me, so please bear with me in case I'm not using the correct technical terminology to describe certain aspects ;-)</p> <p>I ended up using <a href="http://stat.ethz.ch/R-manual/R-devel/library/methods/html/refClass.html" rel="noreferrer">Reference Classes</a> (a way of OOP in R) most of the time because object orientation seems to be the right choice for a lot of the stuff that I'm doing. </p> <p>Now, I was wondering if anyone has some good advice or some experience with respect to implementing the <strong><a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller" rel="noreferrer">MVC</a></strong> (Model View Controller; also known as <strong>MVP</strong>: Model View Presenter) pattern in R, preferably using Reference Classes.</p> <p>I'd also be very interested in info regarding other "standard" design patterns such as <a href="http://en.wikipedia.org/wiki/Observer_pattern" rel="noreferrer">observer</a>, <a href="http://en.wikipedia.org/wiki/Blackboard_system" rel="noreferrer">blackboard</a> etc., but I don't want to make this too broad of a question. I guess the coolest thing would be to see some minimal example code, but any pointer, "schema", diagram or any other idea will also be greatly appreciated!</p> <p>For those interested in similar stuff, I can really recommend the following books:</p> <ol> <li><a href="http://rads.stackoverflow.com/amzn/click/020161622X" rel="noreferrer">The Pragmatic Programmer</a></li> <li><a href="http://rads.stackoverflow.com/amzn/click/0201633612" rel="noreferrer">Design Patterns</a></li> </ol> <p><strong>UPDATE 2012-03-12</strong></p> <p>I did eventually come up with a small example of my interpretation of MVC (which might not be totally correct ;-)).</p> <h2>Package Dependencies</h2> <pre><code>require("digest") </code></pre> <h2>Class Definition Observer</h2> <pre><code>setRefClass( "Observer", fields=list( .X="environment" ), methods=list( notify=function(uid, ...) { message(paste("Notifying subscribers of model uid: ", uid, sep="")) temp &lt;- get(uid, .self$.X) if (length(temp$subscribers)) { # Call method updateView() for each subscriber reference sapply(temp$subscribers, function(x) { x$updateView() }) } return(TRUE) } ) ) </code></pre> <h2>Class Definition Model</h2> <pre><code>setRefClass( "Model", fields=list( .X="data.frame", state="character", uid="character", observer="Observer" ), methods=list( initialize=function(...) { # Make sure all inputs are used ('...') .self &lt;- callSuper(...) # Ensure uid .self$uid &lt;- digest(c(.self, Sys.time())) # Ensure hash key of initial state .self$state &lt;- digest(.self$.X) # Register uid in observer assign(.self$uid, list(state=.self$state), .self$observer$.X) .self }, multiply=function(x, ...) { .self$.X &lt;- .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 &lt;- get(.self$uid, envir=.self$observer$.X) temp$state &lt;- .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 &lt;- TRUE # Hash key of current state state &lt;- digest(.self$.X) if (length(.self$state)) { out &lt;- .self$state != state if (out) { # Update state if it has changed .self$state &lt;- state } } if (out) { message(paste("State change detected for model uid: ", .self$uid, sep="")) # Publish state change to observer .self$publish() } return(out) } ) ) </code></pre> <h2>Class Definition Controller and Views</h2> <pre><code>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 &lt;- .self$model$uid envir &lt;- .self$model$observer$.X temp &lt;- get(uid, envir) # Add itself to subscribers of underlying model temp$subscribers &lt;- 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) } ) ) </code></pre> <h2>Class Definition For Representing Dummy Data</h2> <pre><code>setRefClass( "MyData", fields=list( .X="data.frame" ), methods=list( modelMake=function(...){ new("Model", .X=.self$.X) } ) ) </code></pre> <h2>Create Instances</h2> <pre><code>x &lt;- new("MyData", .X=data.frame(a=1:3, b=10:12)) </code></pre> <p>Investigate model characteristics and observer state</p> <pre><code>mod &lt;- x$modelMake() mod$.X &gt; mod$uid [1] "fdf47649f4c25d99efe5d061b1655193" # Field value automatically set when initializing object. # See 'initialize()' method of class 'Model'. &gt; mod$state [1] "6d95a520d4e3416bac93fbae88dfe02f" # Field value automatically set when initializing object. # See 'initialize()' method of class 'Model'. &gt; ls(mod$observer$.X) [1] "fdf47649f4c25d99efe5d061b1655193" &gt; get(mod$uid, mod$observer$.X) $state [1] "6d95a520d4e3416bac93fbae88dfe02f" </code></pre> <p>Note that the object's uid has automatically been registered in the observer upon initialization. That way, controllers/views can subscribe to notifications and we have a 1:n relationship.</p> <p>Instantiate views and controller</p> <pre><code>view1 &lt;- new("View1") view2 &lt;- new("View2") cont &lt;- new("Controller", model=mod, views=list(view1, view2)) </code></pre> <h2>Subscribe</h2> <p>Controller subscribes to notifications of underlying model</p> <pre><code>cont$subscribe() </code></pre> <p>Note that the subscription has been logged in the observer</p> <pre><code>get(mod$uid, mod$observer$.X) </code></pre> <h2>Display Registered Views</h2> <pre><code>&gt; cont$updateView() a b 1 1 10 2 2 11 3 3 12 [1] TRUE </code></pre> <p>There's also a plot window that is opened.</p> <h2>Modify Model</h2> <pre><code>&gt; 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 a b 1 10 100 2 20 110 3 30 120 [1] TRUE </code></pre> <p>Note that both registered views are automatically updated as the underlying model published its state change to the observer, which in turn notified all subscribers (i.e., the controller).</p> <h2>Open Questions</h2> <p>Here's what I feel like I'm not fully understanding yet:</p> <ol> <li>Is this a somewhat correct implementation of the MVC pattern? If not, what did I do wrong?</li> <li>Should "processing" methods (e.g. aggregate data, take subsets etc.) for the model "belong" to the model or the controller class . So far, I always defined everything a specific object can "do" as methods of this very object. </li> <li>Should the controller be sort of a "proxy" controlling every interaction between model and views (sort of "both ways"), or is it only responsible for propagating user input to the model (sort of "one way"?</li> </ol>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload