在 R 中实现标准软件设计模式(专注于 MVC)

Posted

技术标签:

【中文标题】在 R 中实现标准软件设计模式(专注于 MVC)【英文标题】:Implementing standard software design patterns (focus on MVC) in R 【发布时间】:2012-03-29 06:19:06 【问题描述】:

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

我最终大部分时间都使用Reference Classes(R 中的一种 OOP 方式),因为面向对象似乎是我正在做的很多事情的正确选择。

现在,我想知道是否有人对实现 MVC(模型视图控制器;也称为 MVP:模型在 R 中查看 Presenter) 模式,最好使用参考类。

我也对有关其他“标准”设计模式的信息非常感兴趣,例如observer、blackboard 等,但我不想让这个问题过于笼统。我想最酷的事情是看一些最小的示例代码,但任何指针、“模式”、图表或任何其他想法也将不胜感激!

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

    The Pragmatic Programmer Design Patterns

2012-03-12 更新

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

包依赖

require("digest")

类定义观察者

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

类定义模型

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

类定义控制器和视图

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

表示虚拟数据的类定义

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

创建实例

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

调查模型特征和观察者状态

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"

请注意,对象的 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()
  a  b
1 1 10
2 2 11
3 3 12
[1] TRUE

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

修改模型

> 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

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

开放式问题

以下是我觉得我还没有完全理解的内容:

    这是 MVC 模式的正确实现吗?如果没有,我做错了什么? 应该为模型“处理”方法(例如聚合数据、获取子集等)“属于”模型或控制器类。到目前为止,我总是将特定对象可以“做”的所有事情都定义为这个对象的方法。 控制器应该是一种“代理”,控制模型和视图之间的每次交互(有点“双向”),还是只负责将用户输入传播到模型(有点“单向”?

【问题讨论】:

这个问题是重复的:***.com/questions/9519126/the-use-of-design-patterns-in-r。此外,您的问题有多个目标。一种是关于设计模式的一般性,另一种是针对示例的。重复题和本题的重叠部分主要在一般部分。也许您可以将您的问题编辑为仅对您的示例提出的问题。这也减少了您因过于笼统而被关闭的机会,这发生在重复的问题上。 @PaulHiemstra:感谢您提供的信息!我刚刚完成了一个主要的编辑,我想作为一个开始,为了让这个不太宽泛,我主要是在实现 MVC 之后。 【参考方案1】:

    它看起来不错,但我不太确定为什么你的其他类有一个额外的观察者(也许你可以告诉我)通常控制器是观察者。在 R 中这样做是一个非常好的主意,因为当我在 Java 中学习它时,它并不那么容易理解(Java 隐藏了一些好的部分)

    是和否。对这种模式有许多不同的解释。我喜欢在对象中有方法,我会说它属于模型。 一个简单的例子是数独求解器,它在 GUI 中显示求解步骤。让我们把它分成一些可以分成 M、V 和 C 的部分:原始数据(可能是二维数组)、数独函数(计算下一步,...)、GUI、告诉 GUI 一个新的计算步长 我会这样说:M:原始数据 + 数独函数,C:谁告诉 GUI 有关更改/有关 GUI 输入的模型,V:没有任何逻辑的 GUI 其他人将数独功能放入Controller中,也是对的,可能会更好地解决一些问题

    可能有一个像你这样称呼它的“单向”控制器,而视图是模型的观察者 也可以让 Controller 做所有事情而 Model 和 View 不知道彼此(看看 Model View Presenter,就是这样)

【讨论】:

非常感谢您的回答。自从我写这篇文章以来已经有一段时间了,所以我实际上必须再仔细看看它才能回答你关于额外观察者的问题;-)

以上是关于在 R 中实现标准软件设计模式(专注于 MVC)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Tkinter 中实现 MVC 模式

在哪里可以找到在 ASP .NET MVC2 中实现密码恢复的 C# 示例代码

csharp 在asp.net mvc中实现工作单元模式

如何将变量传递给已经在 R 的参数中实现非标准评估的函数?

MVC中实现加载更多

什么是三范式