В настоящее время я много читаю о программной инженерии, проектировании программного обеспечения, шаблонах проектирования и т. Д. Исходя из совершенно другого фона, это все новые интересные вещи для меня, поэтому, пожалуйста, потерпите меня, если я не использую правильная техническая терминология для описания определенных аспектов; -)
Я заканчивал тем, что использовал Справочные классы (способ ООП в R) большую часть времени, потому что объектная ориентация кажется правильным выбором для многих вещей, которые я делаю.
Теперь мне интересно, есть ли у кого-нибудь полезные советы или опыт в отношении реализации MVC (Model View Controller; также известен как MVP : Шаблон представления модели) в R, предпочтительно с использованием эталонных классов.
Мне также была бы очень интересна информация о других "стандартных" шаблонах проектирования, таких как наблюдатель , доска и т. Д., Но я не хочу делать это слишком широким вопроса. Я думаю, что самое классное было бы увидеть минимальный пример кода, но любой указатель, «схема», диаграмма или любая другая идея также будут высоко оценены!
Для тех, кто интересуется подобными вещами, могу порекомендовать следующие книги:
- Прагматичный программист
- Шаблоны проектирования
ОБНОВЛЕНИЕ 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
Обратите внимание, что оба зарегистрированных представления автоматически обновляются, когда базовая модель публикует информацию об изменении своего состояния для наблюдателя, который, в свою очередь, уведомляет всех подписчиков (т. Е. Контроллер).
Открытые вопросы
Вот то, что я чувствую, я еще не до конца понимаю:
- Является ли это несколько правильной реализацией шаблона MVC? Если нет, то что я сделал не так?
- Должны ли методы "обработки" (например, агрегировать данные, брать подмножества и т. Д.) Для модели "принадлежать" модели или классу контроллера. До сих пор я всегда определял все, что конкретный объект может «делать» как методы этого самого объекта.
- Должен ли контроллер быть своего рода "прокси", управляющим каждым взаимодействием между моделью и представлениями (своего рода "оба пути"), или он отвечает только за распространение пользовательского ввода в модель (своего рода "в одну сторону"?