Actuellement, je lis beaucoup sur le génie logiciel, la conception de logiciels, les modèles de conception, etc. Comme je viens d'un milieu totalement différent, tout cela est fascinant et nouveau pour moi, alors soyez indulgent si je n'utilise pas la bonne terminologie technique pour décrire certains aspects ;-)
J'ai fini par utiliser Classes de référence (une façon de faire de la POO en R) la plupart du temps parce que l'orientation objet semble être le bon choix pour beaucoup de choses que je fais.
Maintenant, je me demandais si quelqu'un avait de bons conseils ou une certaine expérience en ce qui concerne la mise en œuvre de la MVC (Model View Controller ; également connu sous le nom de MVP : Model View Presenter) dans R, de préférence en utilisant des classes de référence.
Je serais également très intéressé par des informations concernant d'autres modèles de conception "standard" tels que observateur , tableau noir etc., mais je ne veux pas que la question soit trop vaste. Je suppose que la chose la plus cool serait de voir un exemple de code minimal, mais tout indice, "schéma", diagramme ou toute autre idée sera également très apprécié !
Pour ceux qui s'intéressent à des sujets similaires, je peux vraiment recommander les livres suivants :
MISE À JOUR 2012-03-12
J'ai fini par trouver un petit exemple de mon interprétation de MVC (qui n'est peut-être pas totalement correcte ;-)).
Dépendances des paquets
require("digest")
Définition de la classe Observateur
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)
}
)
)
Modèle de définition des classes
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)
}
)
)
Définition des classes Contrôleur et vues
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)
}
)
)
Définition des classes pour la représentation des données fictives
setRefClass(
"MyData",
fields=list(
.X="data.frame"
),
methods=list(
modelMake=function(...){
new("Model", .X=.self$.X)
}
)
)
Créer des instances
x <- new("MyData", .X=data.frame(a=1:3, b=10:12))
Étudier les caractéristiques du modèle et l'état de l'observateur
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"
Notez que l'uid de l'objet a été automatiquement enregistré dans l'observateur lors de l'initialisation. De cette façon, les contrôleurs/vues peuvent s'abonner aux notifications et nous avons une relation 1:n.
Instanciation des vues et du contrôleur
view1 <- new("View1")
view2 <- new("View2")
cont <- new("Controller", model=mod, views=list(view1, view2))
S'abonner à
Le contrôleur s'abonne aux notifications du modèle sous-jacent
cont$subscribe()
Notez que l'abonnement a été enregistré dans l'observateur.
get(mod$uid, mod$observer$.X)
Afficher les vues enregistrées
> cont$updateView()
a b
1 1 10
2 2 11
3 3 12
[1] TRUE
Une fenêtre d'intrigue s'ouvre également.
Modifier le modèle
> 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
Notez que les deux vues enregistrées sont automatiquement mises à jour car le modèle sous-jacent a publié son changement d'état à l'observateur, qui à son tour a notifié tous les abonnés (c'est-à-dire le contrôleur).
Questions ouvertes
Voici ce que j'ai l'impression de ne pas encore bien comprendre :
- S'agit-il d'une mise en œuvre relativement correcte du modèle MVC ? Si non, qu'est-ce que j'ai fait de mal ?
- Les méthodes de "traitement" (par exemple, agréger des données, prendre des sous-ensembles, etc.) pour le modèle doivent-elles "appartenir" au modèle ou à la classe du contrôleur ? Jusqu'à présent, j'ai toujours défini tout ce qu'un objet spécifique peut "faire" comme des méthodes de cet objet même.
- Le contrôleur doit-il être une sorte de "proxy" contrôlant chaque interaction entre le modèle et les vues (en quelque sorte "dans les deux sens"), ou est-il seulement responsable de la propagation des entrées utilisateur vers le modèle (en quelque sorte "dans un seul sens") ?