在R中实现标准软件设计模式(关注MVC) [英] Implementing standard software design patterns (focus on MVC) in R

查看:109
本文介绍了在R中实现标准软件设计模式(关注MVC)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前,我正在阅读很多有关软件工程,软件设计,设计模式等的内容。从完全不同的背景来看,这对我来说都是新的迷人的东西,所以请忍受我,以防我不使用正确的技术术语来描述某些方面; - )



我最终使用参考类(ROP中的一种OOP方式)大部分时间是因为对象方向似乎是很多的正确选择我正在做的事情



现在,我想知道有没有人对实现 MVC (Model View Controller;也称为 MVP :在R中,最好使用参考类,模型视图演示者)模式。



我也对其他标准设计模式的信息非常感兴趣,例如观察者黑板等,但我不想让这个问题太广泛。我想最酷的事情是看一些最小的示例代码,但任何指针,架构,图表或任何其他想法也将不胜感激!



对于那些对类似的东西感兴趣,我可以推荐以下书籍:


  1. 实用程序员

  2. 设计模式

更新2012-03-12 / p>

我最终提出了一个我对MVC的解释的一个例子(可能不完全正确; - ))。



包依赖关系



  require(digest)
pre>

类定义Observer



  setRefClass(
观察者,
fields = list(
.X =environment
),
methods = list (
notify = function(uid,...){
message(粘贴(通知用户型号uid:,uid,sep =))
temp < (uid,.self $ .X)
if(length(temp $ subscribers)){
#每个订阅者参考的调用方法updateView()
sapply(temp $ subscribers,function(x ){
x $ updateView()
})
}
return(TRUE)
}




类定义模型



  setRefClass(
Model,
fields = list(
.X =data.frame,
state =character,
uid =character ,
observer =Observer
),
methods = list(
initialize = function(...){
#确保使用所有输入(' ...')
.self< - callSuper(...)
#确保uid
。$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X )
#在观察者注册uid
assign(.self $ uid,list(state = .self $ state),.self $ observer $ .X)
.self
} ,
multiply = function(x,...){
.self $ .X< - .X * x
#句柄状态变化
statechangeDetect()
return(TRUE)
},
publish = function(...){
message(paste(发布状态更改为模型uid:,
.self $ uid ,sep =))
#将当前状态发布给观察者
如果(!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)
临时$状态< - .self $ state
assign(.self $ uid,temp,.self $ observer $ .X)
}
#使观察者通知所有订阅者
.self $ observer $ notify(uid = .self $ uid)
return(TRUE)
},
statechangeDetect = function(...){
out< - TRUE
#当前状态的$ h
状态< - digest(.self $ .X)
if(length(.self $ state)){
out< - .self $ state!= state
if(out){
#更新状态如果它已更改
.self $ state< - state
}
}
if(out){
message(paste(State change detected for model uid:,
.self $ uid,sep =))
#将状态更改发布给观察者
.self $ publish()
}
retu rn(out)
}




类定义控制器和视图



  setRefClass(
Controller,
fields = list(
model =Model,
views =list
),
methods = list(
multiply = function(x,...){
#模型的方法
.self $ model $ multiply(x)
},
subscribe = function(...){
uid< - .self $ model $ uid
envir< - .self $ model $ observer $ .X
temp < - get(uid,envir)
#将其自身添加到底层模型的订阅者
temp $ subscribers< ; c(temp $ subscriber,.self)
assign(uid,temp,envir)
},
updateView = function(...){
#每个注册视图
sapply(.self $ views,function(x){
x $ disp ($ self $ model)
})
return(TRUE)
}


setRefClass(
View1
methods = list(
display = function(model,x = 1,y = 2,...){
plot(x = model $ .X [,x],y =模型$ .X [,y])
}


setRefClass(
View2,
methods = list(
display = function(model,...){
print(model $ .X)
}


pre>

表示虚拟数据的类定义



  setRefClass(
MyData,
fields = list(
.X =data.frame
),
methods = list(
modelMake = function 。){
new(Model,.X = .self $ .X)
}


pre>

创建实例



  x<  -  new(MyData ,.X = data.frame(a = 1:3,b = 10:12))



  mod<  -  x $ modelMake()
mod $ .X

> mod $ uid
[1]fdf47649f4c25d99efe5d061b1655193
#初始化对象时自动设置字段值。
#看到'Model'类的'initialize()'方法。

> mod $ state
[1]6d95a520d4e3416​​bac93fbae88dfe02f
#初始化对象时自动设置字段值。
#看到'Model'类的'initialize()'方法。

> ls(mod $ observer $ .X)
[1]fdf47649f4c25d99efe5d061b1655193

> get(mod $ uid,mod $ observer $ .X)
$ state
[1]6d95a520d4e3416​​bac93fbae88dfe02f

请注意,在初始化时,对象的uid已经自动注册在观察者中。这样,控制器/视图可以订阅通知,并且我们有1:n的关系。



实例化视图和控制器

  view1 < -  new(View1)
view2 < - new(View2)
cont< - new(Controller model = mod,views = list(view1,view2))



订阅



控制器订阅底层模型的通知

  cont $ subscribe()

请注意,订阅已被记录在观察者中

  get(mod $ uid,mod $ observer $ .X)



显示注册视图



 > cont $ updateView()
ab
1 1 10
2 2 11
3 3 12
[1] TRUE
/ pre>

还有一个打开的绘图窗口。



修改模型



 > cont $ model $ multiply(x = 10)
模型uid检测到状态更改:fdf47649f4c25d99efe5d061b1655193
模型uid发布状态更改:fdf47649f4c25d99efe5d061b1655193
通知模型uid的订阅者:fdf47649f4c25d99efe5d061b1655193
ab
1 10 100
2 20 110
3 30 120
[1] TRUE

请注意,两个注册视图都会自动更新,因为底层模型将其状态更改发布给观察者,从而向所有订阅者(即控制器)通知。



打开问题



以下是我觉得我还不完全理解:


  1. 这是一个有点正确的MVC模式的实现吗?如果没有,我做错了什么?

  2. 应该对模型属于模型或控制器类的处理方法(例如聚合数据,采取子集等)。到目前为止,我总是将特定对象的所有内容都定义为这个对象的方法。

  3. 如果控制器是一种代理,控制模型和视图之间的每个交互(两种方式),或者它只负责将用户输入传播到模型一种单向?


解决方案


  1. 看起来相当不错,但是我不太清楚为什么你有一个观察者可以附加到你的其他类(也许你可以告诉我)通常控制器是一个观察者,这是一个很好的主意,在这样做,因为当我学到它在Java中并不那么容易理解(Java隐藏了一些很好的部分)


  2. 是和否。对这种模式有很多不同的解释。我喜欢在Object中使用方法,我会说它属于模型
    一个简单的例子将是一个数独解决器,显示了GUI中的解决步骤,我们将其分成一些可以分为M,V和C:原始数据(2D数组可能),数独函数(calc next ste p,...),GUI,谁告诉GUI一个新的步骤计算
    我会这样说:M:原始数据+数独功能,C:谁告诉GUI关于变化/关于GUI输入的模型,V:没有任何逻辑的
    的GUI将数独功能放入控制器,对于某些问题也是正确的,可能会更好。


  3. 可能有一个单向控制器,就像您称之为视图是模型的观察者
    还可以让控制器执行所有操作,而模型和视图不会彼此认识(看看模型视图演示者,就是这样)



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 ;-)

I ended up using Reference Classes (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.

Now, I was wondering if anyone has some good advice or some experience with respect to implementing the MVC (Model View Controller; also known as MVP: Model View Presenter) pattern in R, preferably using Reference Classes.

I'd also be very interested in info regarding other "standard" design patterns such as observer, blackboard 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!

For those interested in similar stuff, I can really recommend the following books:

  1. The Pragmatic Programmer
  2. Design Patterns

UPDATE 2012-03-12

I did eventually come up with a small example of my interpretation of MVC (which might not be totally correct ;-)).

Package Dependencies

require("digest")

Class Definition Observer

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 Controller and Views

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)
        }
    )
)

Class Definition 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))

Investigate model characteristics and observer state

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 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.

Instantiate views and controller

view1 <- new("View1")
view2 <- new("View2")
cont  <- new("Controller", model=mod, views=list(view1, view2))

Subscribe

Controller subscribes to notifications of underlying model

cont$subscribe()

Note that the subscription has been logged in the observer

get(mod$uid, mod$observer$.X)

Display Registered Views

> cont$updateView()
  a  b
1 1 10
2 2 11
3 3 12
[1] TRUE

There's also a plot window that is opened.

Modify 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
   a   b
1 10 100
2 20 110
3 30 120
[1] TRUE

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).

Open Questions

Here's what I feel like I'm not fully understanding yet:

  1. Is this a somewhat correct implementation of the MVC pattern? If not, what did I do wrong?
  2. 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.
  3. 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"?

解决方案

  1. It looks quite good, but I'm not so sure why you have an Observer additional to your other classes (maybe you can tell me) Usually the Controller IS an Observer. It's a really good idea to do this in R because when I learned it in Java it was not so easy to understand (Java hides some of the good parts)

  2. Yes and No. There are many different interpretations of this pattern. I like to have the methods in the Object, I'd say it belongs to the model. A simple example would be a sudoku solver that shows the solving steps in a GUI. Let's split it into some parts that can be separated into M, V and C: the raw data (2D array maybe), the sudoku functions (calc next step, ...), the GUI, someone who tells the GUI that a new step was calculated I'd put it like this: M: raw data + sudoku functions, C: who tells the GUI about changes / the model about GUI inputs, V: GUI without any logic others put the sudoku function into the Controller, is also right and might work better for some problems

  3. It's possible to have a "one way" controller like you call it and the View is an Observer of the model It's also possible to let the Controller do everything and Model and View don't know each other (have a look at Model View Presenter, that's about that)

这篇关于在R中实现标准软件设计模式(关注MVC)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆