56 votes

Mise en œuvre de modèles de conception de logiciels standard (accent mis sur MVC) dans R

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 :

  1. Le programmeur pragmatique
  2. Modèles de conception

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 :

  1. 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 ?
  2. 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.
  3. 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") ?

3voto

ExpectoPatronum Points 397
  1. Cela semble assez bon, mais je ne suis pas sûr de la raison pour laquelle vous avez un observateur en plus de vos autres classes (peut-être pouvez-vous me le dire) Habituellement, le contrôleur est un observateur. C'est une très bonne idée de faire ça en R parce que quand je l'ai appris en Java, ce n'était pas si facile à comprendre (Java cache certaines des bonnes parties).

  2. Oui et non. Il existe de nombreuses interprétations différentes de ce motif. J'aime avoir les méthodes dans l'objet, je dirais qu'il appartient au modèle. Un exemple simple serait un solveur de sudoku qui montre les étapes de la résolution dans une interface graphique. Divisons-le en plusieurs parties qui peuvent être séparées en M, V et C : les données brutes (tableau 2D peut-être), les fonctions sudoku (calculer l'étape suivante, ...), l'interface graphique, quelqu'un qui indique à l'interface graphique qu'une nouvelle étape a été calculée. Je dirais que c'est comme ça : M : données brutes + fonctions sudoku, C : qui informe l'interface graphique des changements / le modèle des entrées de l'interface graphique, V : interface graphique sans aucune logique. d'autres placent la fonction sudoku dans le contrôleur, ce qui est également correct et pourrait mieux fonctionner pour certains problèmes.

  3. Il est possible d'avoir un contrôleur "à sens unique" comme vous l'appelez et la vue est un observateur du modèle. Il est également possible de laisser le contrôleur tout faire et que le modèle et la vue ne se connaissent pas (regardez Model View Presenter, c'est à peu près ça).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X