ventajas - Implementación de patrones de diseño de software estándar(enfoque en MVC) en R
patrones de diseño de software ejemplos (1)
En la actualidad, estoy leyendo mucho sobre Ingeniería de Software, Diseño de Software, Patrones de Diseño, etc. Procedente de un entorno totalmente diferente, eso es algo nuevo y fascinante para mí, así que por favor tengan paciencia conmigo en caso de que no utilice la terminología técnica correcta. para describir ciertos aspectos ;-)
Terminé usando clases de referencia (una forma de OOP en R) la mayor parte del tiempo porque la orientación del objeto parece ser la opción correcta para muchas de las cosas que estoy haciendo.
Ahora, me preguntaba si alguien tiene algún buen consejo o alguna experiencia con respecto a la implementación del patrón MVC (Modelo View Controller, también conocido como MVP : Model View Presenter) en R, preferiblemente utilizando clases de referencia.
También me interesaría mucho la información sobre otros patrones de diseño "estándar", como el observer , el blackboard , etc., pero no quiero que sea una pregunta demasiado amplia. ¡Creo que lo mejor sería ver un código de ejemplo mínimo, pero cualquier puntero, "esquema", diagrama o cualquier otra idea también será muy apreciado!
Para aquellos interesados en cosas similares, realmente puedo recomendar los siguientes libros:
ACTUALIZACIÓN 2012-03-12
Eventualmente presenté un pequeño ejemplo de mi interpretación de MVC (que podría no ser del todo correcto ;-)).
Dependencias del paquete
require("digest")
Observador de definición de clase
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)
}
)
)
Modelo de definición de clase
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)
}
)
)
Controlador de definición de clase y vistas
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)
}
)
)
Definición de Clase para Representar Datos Dummy
setRefClass(
"MyData",
fields=list(
.X="data.frame"
),
methods=list(
modelMake=function(...){
new("Model", .X=.self$.X)
}
)
)
Crear instancias
x <- new("MyData", .X=data.frame(a=1:3, b=10:12))
Investigar las características del modelo y el estado observador
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"
Tenga en cuenta que el uid del objeto se ha registrado automáticamente en el observador después de la inicialización. De esta forma, los controladores / vistas pueden suscribirse a las notificaciones y tenemos una relación 1: n.
Crear instancias de vistas y controlador
view1 <- new("View1")
view2 <- new("View2")
cont <- new("Controller", model=mod, views=list(view1, view2))
Suscribir
El controlador se suscribe a las notificaciones del modelo subyacente
cont$subscribe()
Tenga en cuenta que la suscripción ha sido registrada en el observador
get(mod$uid, mod$observer$.X)
Mostrar vistas registradas
> cont$updateView()
a b
1 1 10
2 2 11
3 3 12
[1] TRUE
También hay una ventana de diagrama que se abre.
Modificar modelo
> 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
Tenga en cuenta que ambas vistas registradas se actualizan automáticamente ya que el modelo subyacente publicó su cambio de estado en el observador, que a su vez notificó a todos los suscriptores (es decir, el controlador).
Preguntas abiertas
Esto es lo que siento que aún no entiendo completamente:
- ¿Es esta una implementación algo correcta del patrón MVC? Si no, ¿qué hice mal?
- Deberían los métodos de "procesamiento" (por ejemplo, datos agregados, subconjuntos de tomas, etc.) para el modelo "pertenecer" al modelo o la clase de controlador. Hasta ahora, siempre he definido todo lo que un objeto específico puede "hacer" como métodos de este mismo objeto.
- ¿Debería el controlador ser una especie de "proxy" que controla cada interacción entre el modelo y las vistas (en cierto modo "en ambos sentidos"), o solo es responsable de propagar la entrada del usuario al modelo (tipo de "unidireccional"?
Se ve bastante bien, pero no estoy muy seguro de por qué tienes un observador adicional a tus otras clases (quizás puedas decirme). Usualmente el controlador es un observador. Es una muy buena idea hacer esto en R porque cuando lo aprendí en Java no fue tan fácil de entender (Java oculta algunas de las partes buenas)
Sí y no. Hay muchas interpretaciones diferentes de este patrón. Me gusta tener los métodos en el objeto, diría que pertenece al modelo. Un ejemplo simple sería un solucionador de sudoku que muestra los pasos de solución en una GUI. Vamos a dividirlo en algunas partes que pueden separarse en M, V y C: los datos brutos (una matriz 2D tal vez), las funciones sudoku (calcular el siguiente paso, ...), la GUI, alguien que le dice a la GUI que una nueva paso fue calculado lo pondría así: M: datos brutos + funciones sudoku, C: quien le dice a la GUI sobre cambios / el modelo sobre entradas GUI, V: GUI sin ninguna lógica otros ponen la función sudoku en el controlador, es también es correcto y podría funcionar mejor para algunos problemas
Es posible tener un controlador de "una sola dirección" como lo llama y la Vista es un Observador del modelo. También es posible dejar que el Controlador haga todo y el Modelo y la Vista no se conocen entre sí (eche un vistazo al Presentador de Vistas de Modelos , eso es todo eso)