Подтвердить что ты не робот

Внедрение стандартных шаблонов проектирования программного обеспечения (фокус на MVC) в R

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

В большинстве случаев я использовал Reference Classes (способ OOP в R), поскольку объектная ориентация кажется правильным выбором для многое из того, что я делаю.

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

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

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

ОБНОВЛЕНИЕ 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? Если нет, что я сделал не так?
  • Должны ли методы "обработки" (например, агрегировать данные, принимать подмножества и т.д.) для модели "принадлежать" модели или классу контроллера. До сих пор я всегда определял все, что конкретный объект может "делать" как методы этого самого объекта.
  • Если контроллер является своего рода "прокси", контролирующим каждое взаимодействие между моделью и представлениями (вроде как "в обоих направлениях" ), или он несет ответственность только за распространение пользовательского ввода в модель (вроде "один способ"?
4b9b3361

Ответ 1

  • Это выглядит неплохо, но я не уверен, почему у вас есть Observer для других классов (возможно, вы можете мне сказать). Обычно контроллер является наблюдателем. Это действительно хорошая идея сделать это в R, потому что, когда я узнал об этом в Java, это было не так просто понять (Java скрывает некоторые из хороших частей)

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

  • Возможно иметь контроллер "один путь", как вы его называете, и View является наблюдателем модели Также можно позволить контроллеру делать все, а Model и View не знают друг друга (посмотрите на Model View Presenter, что об этом)