Реализация стандартных шаблонов проектирования программного обеспечения (фокус на MVC) в R - PullRequest
56 голосов
/ 13 марта 2012

В настоящее время я много читаю о программной инженерии, проектировании программного обеспечения, шаблонах проектирования и т. Д. Исходя из совершенно другого фона, это все новые интересные вещи для меня, поэтому, пожалуйста, потерпите меня, если я не использую правильная техническая терминология для описания определенных аспектов; -)

Я заканчивал тем, что использовал Справочные классы (способ ООП в R) большую часть времени, потому что объектная ориентация кажется правильным выбором для многих вещей, которые я делаю.

Теперь мне интересно, есть ли у кого-нибудь полезные советы или опыт в отношении реализации MVC (Model View Controller; также известен как MVP : Шаблон представления модели) в R, предпочтительно с использованием эталонных классов.

Мне также была бы очень интересна информация о других "стандартных" шаблонах проектирования, таких как наблюдатель , доска и т. Д., Но я не хочу делать это слишком широким вопроса. Я думаю, что самое классное было бы увидеть минимальный пример кода, но любой указатель, «схема», диаграмма или любая другая идея также будут высоко оценены!

Для тех, кто интересуется подобными вещами, могу порекомендовать следующие книги:

  1. Прагматичный программист
  2. Шаблоны проектирования

ОБНОВЛЕНИЕ 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"

Обратите внимание, что идентификатор объекта автоматически регистрируется в наблюдателе при инициализации. Таким образом, контроллеры / представления могут подписываться на уведомления, и мы имеем отношение 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

Обратите внимание, что оба зарегистрированных представления автоматически обновляются, когда базовая модель публикует информацию об изменении своего состояния для наблюдателя, который, в свою очередь, уведомляет всех подписчиков (т. Е. Контроллер).

Открытые вопросы

Вот то, что я чувствую, я еще не до конца понимаю:

  1. Является ли это несколько правильной реализацией шаблона MVC? Если нет, то что я сделал не так?
  2. Должны ли методы "обработки" (например, агрегировать данные, брать подмножества и т. Д.) Для модели "принадлежать" модели или классу контроллера. До сих пор я всегда определял все, что конкретный объект может «делать» как методы этого самого объекта.
  3. Должен ли контроллер быть своего рода "прокси", управляющим каждым взаимодействием между моделью и представлениями (своего рода "оба пути"), или он отвечает только за распространение пользовательского ввода в модель (своего рода "в одну сторону"?

1 Ответ

3 голосов
/ 27 июля 2012
  1. Это выглядит довольно хорошо, но я не совсем уверен, почему у вас есть Наблюдатель, дополнительный к вашим другим классам (возможно, вы можете сказать мне). Обычно Контроллер - это Наблюдатель. Это действительно хорошая идея сделать это в R, потому что, когда я изучил это на Java, это было не так легко понять (Java скрывает некоторые хорошие части)

  2. Да и нет. Существует множество различных интерпретаций этого паттерна. Мне нравится иметь методы в объекте, я бы сказал, что он принадлежит модели. Простым примером будет решатель судоку, который показывает шаги решения в графическом интерфейсе. Давайте разделим его на несколько частей, которые можно разделить на M, V и C: необработанные данные (может быть, 2D-массив), функции судоку (calc next step, ...), GUI, кто-то, кто сообщает GUI, что новый шаг был рассчитан Я бы сказал так: M: необработанные данные + функции судоку, C: кто сообщает GUI об изменениях / модели входов GUI, V: GUI без какой-либо логики другие помещают функцию sudoku в контроллер, это также правильно и может работать лучше для некоторых проблем

  3. Можно иметь «односторонний» контроллер, как вы его называете, и View является наблюдателем модели. Также возможно позволить контроллеру делать все, а Model и View не знают друг друга (посмотрите на Presenter Model View, вот и все)

...