IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Dotnet Discussion :

Quelques questions sur le pattern MVC


Sujet :

Dotnet

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    maa
    maa est déconnecté
    Membre éclairé
    Avatar de maa
    Inscrit en
    Octobre 2005
    Messages
    672
    Détails du profil
    Informations personnelles :
    Âge : 41

    Informations forums :
    Inscription : Octobre 2005
    Messages : 672
    Par défaut Quelques questions sur le pattern MVC
    Bonjour,

    J'ai lu ce tutoriel afin de comprendre comment organiser mes classes en modèles, vues et contrôleurs. En essayant de mettre en place ce pattern, j'ai été confronté aux problèmes suivant:

    J'ai une classe modèle qui possède une propriété collection que j'aimerai représenter d'une manière différente pour les besoin de l'interface. Lorsque cette propriété est modifié, un évènement est levé et le contrôleur averti, met à jour la propriété collection de la vue correspondante.
    Jusqu'à la pas de problème, mais ma classe modèle possède aussi d'autres propriété que j'aimerais également afficher dans l'UI. Faut-il créer des propriété correspondante dans la vue ? Je suppose que oui car le binding ce fait sur la vue, mais cela implique que la vue contienne une référence au modèle et que chaque propriété de la vue renvoie la propriété correspondante du modèle et cela devient rapidement lourd à mettre en place...
    Une autre solution serait de mapper (avec éventuellement un outil comme AutoMapper) les propriétés du modèle à celle de la vue au moment de la création de celle-ci par le contrôleur. L'avantage est que la vue n'a pas besoin de contenir une instance du modèle. L'inconvénient est qu'en settant les propriétés de la vue, le modèle n'est pas modifié.

    Maintenant prenons ce cas simplifié.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    public class Model1
    {
         public Model2 MyProperty {get; set;}
    }
     
    public class Model2
    {
    }
    Si je veux créer une vue pour le Model1, je dois également créer une vue pour le Model2 de cette manière

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    public class View1
    {
         private Model1 model;
         ...
         public View2 MyProperty {get; set;}
    }
     
    public class View2
    {
         private Model1 model;
         ...
    }
    Comme les classe modèles sont plus ou moins toutes liés, je dois créer une vue pour chacune d'elle. Il devient alors très fastidieux de synchroniser les vues et les modèles. Et si ma classe View2 ne faisait finalement que renvoyer les propriétés de Model2, aurait-elle vraiment sa raison d'être ? Cela représenterait beaucoup de code pour pas grand chose...

    Merci d'avance pour vos éclaircissements sur ces questions.

  2. #2
    Membre émérite
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    652
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 652
    Par défaut
    Citation Envoyé par maa Voir le message
    J'ai une classe modèle qui possède une propriété collection que j'aimerai représenter d'une manière différente pour les besoin de l'interface. Lorsque cette propriété est modifié, un évènement est levé et le contrôleur averti, met à jour la propriété collection de la vue correspondante.
    Si c'est le contrôleur qui met à jour la vue, ça me fait plus penser à du MVP que du MVC.

    Mis à part ça, je ne suis pas sûr de bien voir le problème. Le fait que Model1.MyProperty fasse référence à Model2 n'est d'aucune importance pour View1.

    D'ailleurs dans ton exemple, View2 ne devrait pas faire référence à Model2 ? Parce que là ça embrouille un peu :)

    En suivant le principe de MVC tel que décrit par Martin Fowler, View1 ferait référence à Model1 et à son contrôleur (on va dire Controller1 :)

    Un évènement lambda se déclenche sur View1, qui passe le contrôle à Controller1, qui fait des actions sur Model1, qui provoque une modification de Model1 (une collection, quelques propriétés diverses et variées, peu importe), qui déclenche un évènement observé par View1, qui récupère les données du modèle et fait ce qu'elle veut des nouvelles valeurs. Que ce soit un mapping direct ou quelque chose de plus évolué.

    Donc oui, la vue doit avoir une référence sur le modèle et faire la correspondance de toutes les données nécessaires avec les éléments graphiques. C'est après tout son seul job :)

    Si View2 fait référence à Model2 (ou même à Model1 d'ailleurs, peu importe), le même principe s'applique. Donc oui, là aussi, View2 doit s'occuper de synchroniser les données dont elle a besoin.

    La synchronisation elle-même ne devrait pas être si fastidieuse que ça dans le sens où une vue n'a besoin que de faire la correspondance des données qui l'intéressent. Si une autre vue est liée au même modèle mais affiche autre chose, elle fait sa correspondance sur ces autres données. Toutes les synchronisations de vues seront déclenchées en même temps par l'évènement du modèle.

    Si c'est cette partie-là qui te pose un problème, il manque peut-être quelques détails sur le projet pour le mettre en évidence. Peut-être des classes responsables de trop de choses ? Peut-être aussi que MVC n'est pas adapté à ce que tu veux faire ? (ça arrive, c'est pour ça qu'il y a quelques zillions de variantes :)


    En tout cas, là comme ça, un modèle avec une collection et quelques propriétés, deux vues basées dessus qui font la synchro de ce qui les intéresse, ça ne semble pas anormal :)

  3. #3
    maa
    maa est déconnecté
    Membre éclairé
    Avatar de maa
    Inscrit en
    Octobre 2005
    Messages
    672
    Détails du profil
    Informations personnelles :
    Âge : 41

    Informations forums :
    Inscription : Octobre 2005
    Messages : 672
    Par défaut
    Merci pour ta réponse.

    Si c'est le contrôleur qui met à jour la vue, ça me fait plus penser à du MVP que du MVC.
    Alors peut être que je devrais appliquer le pattern MVP. Voici mon scenario:
    Je suis en train de créer une petite application pour définir des jours de livraison pour des commandes. En base de données, il y a une liste de transporteurs et pour chaque transporteurs une liste de jours de livraison possible. Avec mon application, l'utilisateur doit pouvoir modifier la liste des jours de livraison par transport. J'aimerais pour cela représenter dans une datagrid tous les jours de la semaine et que l'utilisateur puisse cocher les jours de livraison possibles. En base de donnée, j'ai donc liste de jours de livraison possibles (si un jour n'est pas possible, il ne figure pas dans la table) et côté interface tous les jours sont présentés (si un jour n'est pas possible, il est présent mais décoché).
    Je dois donc compléter la collection de la vue en me basant sur la collection du modèle. Où doit se faire se travail? Je pensais que c'était le rôle du contrôleur.

    Mis à part ça, je ne suis pas sûr de bien voir le problème. Le fait que Model1.MyProperty fasse référence à Model2 n'est d'aucune importance pour View1.
    Ce que je voulais dire c'est que View1 doit forcément posséder alors une collection de View2 et ça nous oblige donc à créer aussi une classe View2, même si une vue de Model2 n'était pas nécessaire... A moins que View2 puisse posséder une collection de Model2 ?

    D'ailleurs dans ton exemple, View2 ne devrait pas faire référence à Model2 ? Parce que là ça embrouille un peu
    Oui, erreur de copier-coller.

    La synchronisation elle-même ne devrait pas être si fastidieuse que ça dans le sens où une vue n'a besoin que de faire la correspondance des données qui l'intéressent. Si une autre vue est liée au même modèle mais affiche autre chose, elle fait sa correspondance sur ces autres données. Toutes les synchronisations de vues seront déclenchées en même temps par l'évènement du modèle.
    Ok mais ça fait quand même tout une série d'évenements à mettre en place...
    Mais si par exemple, Model1 possède un propriété Name de type string que l'on veut afficher telle quelle dans l'UI. Laquelle des ces solutions doit-on mettre en place ?

    - faire une propriété "Name" correspondante dans la vue et la synchroniser avec le modèle par évenement
    - faire une propriété "Name" dans la vue qui renvoie simplement vers la proprité correspondante du modèle (get{return Model.Name;} set {Model.Name = value})
    - ne pas faire de propriété Name dans la vue correspondante et faire le binding sur le path "Model.Name". Cela implique que Model soit exposé publiquement par la vue.

  4. #4
    Membre émérite
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    652
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 652
    Par défaut
    Citation Envoyé par maa Voir le message
    Je dois donc compléter la collection de la vue en me basant sur la collection du modèle. Où doit se faire se travail? Je pensais que c'était le rôle du contrôleur.
    Dans le cadre du MVC (toujours pour celui décrit par Fowler), le contrôleur réagit aux actions effectuées sur la vue pour agir sur le modèle. La vue réagit aux évènements du modèle et se sert de lui pour s'afficher.

    Le contrôleur ignore toute la partie mise à jour de la vue. Tout repose exclusivement sur le mécanisme d'évènements entre la vue et le modèle.

    Citation Envoyé par maa Voir le message
    Ce que je voulais dire c'est que View1 doit forcément posséder alors une collection de View2 et ça nous oblige donc à créer aussi une classe View2, même si une vue de Model2 n'était pas nécessaire... A moins que View2 puisse posséder une collection de Model2 ?
    Collection de View2 == liste des jours affichés, c'est ça ?
    Si la liste est simple, il n'y a peut-être pas besoin de faire une vue séparée pour chaque ligne. Si View1 expose le nécessaire pour que son contrôleur puisse réagir aux évènements sur le datagrid et savoir comment les répercuter sur le modèle, tu pourrais t'en tirer avec une simple collection d'objets au niveau de View1 qui serait alimentée à partir des données du modèle puis bindée avec le datagrid. La classe des objets en question serait un wrapper des objets 'jour de livraison' du domaine, adaptée pour l'affichage.

    Sinon, si tu dois vraiment avoir une deuxième vue... entre une collection de View2 avec chacune une instance de Model2 et une seule View2 représentant la liste avec une collection de Model2... dans les deux cas ça revient un peu au même.

    J'ai un peu de mal à faire la correspondance entre Model1/Model2 et ton scénario cela dit. Si Model1 permet de récupérer la liste des jours sélectionnés + un évènement quand cette liste change, ça ne suffirait pas à ta (ou tes) vue(s) pour se débrouiller ?

    Tu aurais par exemple View1/Controller1 avec référence sur Model1, contenant une View2 et réagissant à tous les évènements utiles, autres que ceux concernant View2.
    View2/Controller2 avec référence aussi sur Model1, contenant une collection de jours 'bindable', réagissant aux évènements de Model1 liés aux jours.

    Chaque vue s'occupe de ses affaires, le modèle regroupe ce dont a besoin ce 'bloc'. Regroupé sur Model1 ou réparti sur Model1 et 2, ça c'est une autre question :)

    Dans tous les cas, tu n'es pas obligé d'avoir une instance de modèle pour chaque jour. Vu que tu traites une collection de jours, il semble logique que le modèle te fournisse... bah une collection de jours :)

    Le modèle n'est pas "l'objet à afficher". Ça peut être le cas si les traitements sont vraiment simples, mais ça correspond plutôt à une partie de la couche métier qui permet d'effectuer les traitements nécessaires (pour le contrôleur) et de récupérer les données utilisées pour l'affichage. Tu n'as pas forcément besoin d'avoir les propriétés Name & co directement sur Model1. Tu peux plutôt demander à Model1 de te renvoyer l'objet (ou la collection) à afficher. Et si cet objet est directement bindable dans la vue... bah faut étudier mais pourquoi pas.

    Citation Envoyé par maa Voir le message
    Ok mais ça fait quand même tout une série d'évenements à mettre en place...
    Mais si par exemple, Model1 possède un propriété Name de type string que l'on veut afficher telle quelle dans l'UI. Laquelle des ces solutions doit-on mettre en place ?

    - faire une propriété "Name" correspondante dans la vue et la synchroniser avec le modèle par évenement
    - faire une propriété "Name" dans la vue qui renvoie simplement vers la proprité correspondante du modèle (get{return Model.Name;} set {Model.Name = value})
    - ne pas faire de propriété Name dans la vue correspondante et faire le binding sur le path "Model.Name". Cela implique que Model soit exposé publiquement par la vue.
    Pas la 2è déjà, c'est le contrôleur qui est chargé de répercuter les actions (donc les modifications) sur le modèle.

    La 3è rejoint grosso modo le principe d'obtenir du modèle un objet directement bindé dans l'interface. Que ce soit le modèle lui-même me titille un peu (il est là pour faire le boulot, pas pour se faire binder sauvagement :), mais on va dire que c'est de la famille. Dans ce cas-là, le mécanisme de databinding peut jouer le rôle de contrôleur. Après tout il est là pour ça. Réagir aux évènements de la vue et transmettre le traitement au modèle. Par contre ça s'éloigne du MVC 'pur'. Dans ce cas-là, ton contrôleur n'est plus le seul en charge de transmettre les actions au modèle. Moins pratique pour les tests unitaires notamment.

    La 1ère est la version 'fait main', sans databinding, mais c'est le même principe. Tu mets la propriété Name dans la vue, tu l'alimentes à partir du modèle, et quand le modèle balance son évènement Updated, tu remets à jour ta propriété dans la vue. Dans l'autre sens, quand tu fais des modifications, la vue transmet l'action au contrôleur, qui récupère la valeur de la propriété Name (et des autres) et va dire au modèle de se mettre à jour. Et le cycle recommence.


    Cette version-là façon MVP (du moins façon Passive View, la plus pratique pour les tests unitaires :), ça donne :
    Tu mets la propriété Name dans la vue. Au chargement, le Presenter l'alimente à partir du modèle. Quand tu fais des modifications dans la vue, le Presenter récupère l'action (comme en MVC, évènement ou appel direct depuis la vue), va dire au modèle de faire son boulot, puis remet la vue à jour. Pas forcément besoin d'évènement du côté du modèle si le Presenter est la seule source de changement sur les données affichées. Il sait quels traitements ont été effectués vu que c'est lui qui les lance et il sait quelles parties de l'interface remettre à jour si nécessaire.

    Dans le cas d'un objet renvoyé par le modèle et directement bindable dans l'interface, c'est pareil, c'est le Presenter qui récupère l'objet en question et qui le passe ensuite à la vue. Plus besoin de recopier les propriétés. Mais pour ça il faut vraiment que la structure de l'objet bindé corresponde bien à l'affichage. S'il y a des divergences, il faut faire l'adaptation.

    La vue elle-même ne fait grosso modo plus rien. Elle n'a en particulier plus de référence vers le modèle (et pas forcément vers le Presenter non plus d'ailleurs). Et ça veut dire que tu peux tester l'intégralité de la couche de présentation en injectant une fausse vue et un faux modèle au Presenter, et en vérifiant qu'il fait correctement le pont dans les deux sens. Mais là on s'éloigne du sujet d'origine :)

  5. #5
    maa
    maa est déconnecté
    Membre éclairé
    Avatar de maa
    Inscrit en
    Octobre 2005
    Messages
    672
    Détails du profil
    Informations personnelles :
    Âge : 41

    Informations forums :
    Inscription : Octobre 2005
    Messages : 672
    Par défaut
    Oublions Model1, Model2, View1 et View2. Je les ai pris pour parler d'un cas général et puis après j'ai basculé sur mon problème particulier. C'est vrai que ça prête à confusion.
    Voici, mon code actuel, rassemblé en une classe:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class Shipping
    {
    	//Collection fournies par les donnée de la bdd
    	private ObservableCollection<DeliveryDay> _Days;
    	public ObservableCollection<DeliveryDay> Days
    	{
    		get { return _Days; }
    		set
    		{
    			_Days = value;
    			AvailableDays = new ObservableCollection<DeliveryDay>();
    			for (int i = 0; i < 7; i++)
    			{
    				var day = _Days.FirstOrDefault(y => y.Day == i) ??
    									new DeliveryDay
    									{
    										Day = i,
    										IsDeliveryPossible = false,
    									};
    				day.DeliveryPossibleChanged += delegate
    				{
    					if (day.IsDeliveryPossible)
    						_Days.Add(day);
    					else
    						_Days.Remove(day);
    				};
    				AvailableDays.Add(day);
    			}
    		}
    	}
     
    	//collection destinée à être bindée à l'UI
    	public ObservableCollection<DeliveryDay> AvailableDays {get; set;}
    }
    La question que je me posait était: si on met la collection AvailableDays dans une class ShippingView, est-ce que cette collection ne devrait pas plutôt être de type : ObservableCollection<DeliveryDayView>, même si à priori je n'ai pas besoin d'une classe DeliveryDayView qui ne serait qu'une copie du modèle avec une propriété "IsDeliveryPossible" en plus qui quand elle est setté lève un evenement "DeliveryPossibleChanged".

    Si j'ai bien compris, en MVC ça donnerait donc

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
        public class ShippingModel : INotifyPropertyChanged
        {
            private ObservableCollection<DeliveryDay> _days;
            public ObservableCollection<DeliveryDay> Days
            {
                get { return _days; }
                set
                {
                    _days = value;
                    if(PropertyChanged!=null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Days"));
                }
            }
     
            #region INotifyPropertyChanged Members
     
            public event PropertyChangedEventHandler PropertyChanged;
     
            #endregion
        }
     
        public class ShippingView
        {
            private ShippingModel model;
     
            public ShippingView(ShippingModel model)
            {
                this.model = model;
                model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
            }
     
            void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                AvailableDays = new ObservableCollection<DeliveryDay>();
                for (int i = 0; i < 7; i++)
                {
                    var day = model.Days.FirstOrDefault(y => y.Day == i) ??
                                        new DeliveryDay
                                        {
                                            Day = i,
                                            IsDeliveryPossible = false,
                                        };
                    day.DeliveryPossibleChanged += new EventHandler(day_DeliveryPossibleChanged);
                    AvailableDays.Add(day);
                }
            }
     
            void day_DeliveryPossibleChanged(object sender, EventArgs e)
            {
                if (DeliveryDayChanged != null)
                    DeliveryDayChanged(this, new DeliveryDayEventArgs(sender as DeliveryDay));
            }
     
            public ObservableCollection<DeliveryDay> AvailableDays { get; set; }
     
            public event EventHandler<DeliveryDayEventArgs> DeliveryDayChanged;
        }
     
        public class DeliveryDayEventArgs :EventArgs
        {
            public DeliveryDayEventArgs(DeliveryDay deliveryDay)
            {
                DeliveryDay = deliveryDay;
            }
     
            public DeliveryDay DeliveryDay { get; set; }
        }
     
        public class ShippingController
        {
            private ShippingModel model;
     
            public ShippingController(ShippingModel model)
            {
                this.model = model;
                View = new ShippingView(model);
                View.DeliveryDayChanged += new EventHandler<DeliveryDayEventArgs>(View_DeliveryDayChanged);
            }
     
            void View_DeliveryDayChanged(object sender, DeliveryDayEventArgs e)
            {
                DeliveryDay day = e.DeliveryDay;
                if (day.IsDeliveryPossible)
                    model.Days.Add(day);
                else
                    model.Days.Remove(day);
            }
     
            public ShippingView View { get; private set; }
        }
    Le code est plus lourd à écrire et je doute que ce découpage soit vraiment utile. Sans compter que je n'ai pas fais figurer ici d'autres propriétés de ShippingModel à afficher dans l'UI et que je devrait réécrire dans la vue ainsi que toute la plomberie de synchronisation par événement... Et sans compter que je n'ai pas fait de DeliveryDayView, j'ai simplement ajouté la propriété IsDeliveryPossible et l'événement DeliveryPossibleChanged dans le modèle.

    La classe des objets en question serait un wrapper des objets 'jour de livraison' du domaine, adaptée pour l'affichage.
    C'est un peu ce que j'ai fait avec ShippingView non ?

    Tu n'as pas forcément besoin d'avoir les propriétés Name & co directement sur Model1. Tu peux plutôt demander à Model1 de te renvoyer l'objet (ou la collection) à afficher. Et si cet objet est directement bindable dans la vue... bah faut étudier mais pourquoi pas.
    Name & co sont des propriété de l'entité Shipping, mais elles sont a afficher pour représenter les shipping dans l'UI. Faut-il maintenant repercuter ses propriété dans shippingview et les synchroniser ?

    Pas la 2è déjà, c'est le contrôleur qui est chargé de répercuter les actions (donc les modifications) sur le modèle.
    Dommage c'est celui que je sentais le mieux parce que le moins lourd.

    La 1ère est la version 'fait main', sans databinding, mais c'est le même principe.
    Si il y a un binding entre Name de shippingview et l'UI mais il faut ensuite synchroniser le modèle et la vue en passant par le controleur quand cette dernière est modifiée. C'est ça que je trouve lourd.

    la vue transmet l'action au contrôleur, qui récupère la valeur de la propriété Name (et des autres) et va dire au modèle de se mettre à jour.
    euh tu veux dire que c'est le contrôleur qui va mettre à jour le modèle, non ?

    Dans le cas d'un objet renvoyé par le modèle et directement bindable dans l'interface, c'est pareil, c'est le Presenter qui récupère l'objet en question et qui le passe ensuite à la vue. Plus besoin de recopier les propriétés. Mais pour ça il faut vraiment que la structure de l'objet bindé corresponde bien à l'affichage. S'il y a des divergences, il faut faire l'adaptation.
    Je ne vois pas trop ce que tu veux dire par "objet renvoyé par le modèle", ni ce que passer à la vue signifie... Ca voudrait dire que shippingModel serait passé à shippingView par le presenter ? Je ne comprends pas trop le sens.

    Sinon il me semble que j'ai bien compris la différence entre MVP et MVC mais je ne sais pas trop lequel est le mieux adapté à mon cas.

    Merci pour tes explications et conseils.

  6. #6
    Membre émérite
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    652
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 652
    Par défaut
    Citation Envoyé par maa Voir le message
    La question que je me posait était: si on met la collection AvailableDays dans une class ShippingView, est-ce que cette collection ne devrait pas plutôt être de type : ObservableCollection<DeliveryDayView>, même si à priori je n'ai pas besoin d'une classe DeliveryDayView qui ne serait qu'une copie du modèle avec une propriété "IsDeliveryPossible" en plus qui quand elle est setté lève un evenement "DeliveryPossibleChanged".
    Après 3 révisions de ce paragraphe, je penche au final pour une classe DeliveryDayView oui :)

    La notion de DeliveryDay 'possible' et l'évènement associé sont des concepts de la vue. Le modèle n'est pas concerné. Lui traite des jours de livraison, et un jour de livraison est un jour où il peut y avoir une livraison, logique.

    La vue est la seule a avoir besoin de ce 'possible' comme information supplémentaire, donc il serait logique que tout se situe au même niveau.

    Parallèlement, si tu ajoutes tout ça directement sur DeliveryDay, ça veut dire que n'importe quel code utilisant cette classe aura accès à cette notion de jour 'possible' et pourra potentiellement déclencher une réaction en chaîne via des gestionnaires d'évènement dont il n'aura pas connaissance. Ça peut rapidement devenir très embrouillé.

    Citation Envoyé par maa Voir le message
    Si j'ai bien compris, en MVC ça donnerait donc
    Ça y ressemble oui. À vue de nez, tu as un petit problème avec l'attachement sur l'évènement DeliveryPossibleChanged. Vu que la plupart des mêmes jours reviennent à chaque fois (il n'y en a qu'un d'ajouté/supprimé), tu attaches un nouveau gestionnaire en plus de ce qui était déjà en place. Donc ça déclencherait OnDayDeliveryPossibleChanged plusieurs fois pour chaque jour.

    Si tu passes par une classe DeliveryDayView, tu peux simplement recréer complètement la collection AvailableDays à chaque fois. Autre avantage de la séparation :)

    Après, le principal problème est lié à l'utilisation des évènements pour dicter l'exécution du programme. En lisant le code du modèle seul, on n'a pas la moindre idée de ce qui se passe quand PropertyChanged est déclenché. Idem du côté de la vue. Ce qui se passe quand DeliveryDayChanged se lance n'est pas visible.

    Pour le modèle, il n'y a pas le choix, c'est fait exprès. Pour le contrôleur, l'option de permettre à la vue de l'appeler directement pourrait améliorer la lisibilité.

    Citation Envoyé par maa Voir le message
    Le code est plus lourd à écrire et je doute que ce découpage soit vraiment utile.:roll: Sans compter que je n'ai pas fais figurer ici d'autres propriétés de ShippingModel à afficher dans l'UI et que je devrait réécrire dans la vue ainsi que toute la plomberie de synchronisation par événement...
    Pour un traitement simple sur un projet simple, ça peut être de l'overkill oui, clairement :)

    Quand on s'est habitué à séparer tout ça, on le fait quasi-automatiquement, mais à petite échelle ce n'est pas forcément justifié.

    MVC & co gagnent en intérêt au fur et à mesure que vues et modèles gagnent en complexité (et en fonction de l'importance qu'on apporte aux tests unitaires aussi). À court terme ça fait plus de code à écrire et de la plomberie à mettre en place, mais à plus long terme ça permet de faire évoluer l'application bien plus facilement que si tous les traitements sont entassés au même endroit.

    Citation Envoyé par maa Voir le message
    La classe des objets en question serait un wrapper des objets 'jour de livraison' du domaine, adaptée pour l'affichage.
    C'est un peu ce que j'ai fait avec ShippingView non ?
    Ce serait plutôt DeliveryDayView dans ce cas-là. Donc cf plus haut pour l'intérêt (ou non) d'avoir cette classe.

    Citation Envoyé par maa Voir le message
    Name & co sont des propriété de l'entité Shipping, mais elles sont a afficher pour représenter les shipping dans l'UI. Faut-il maintenant repercuter ses propriété dans shippingview et les synchroniser ?
    Yup.

    Citation Envoyé par maa Voir le message
    Dommage c'est celui que je sentais le mieux parce que le moins lourd.
    Cf plus haut aussi donc. Selon la taille et la complexité de l'application, tu n'as peut-être pas besoin de sortir l'artillerie lourde (et encore, ce n'est pas la version la plus complexe :). Ça fait toujours un bon entrainement cela dit.

    Citation Envoyé par maa Voir le message
    Si il y a un binding entre Name de shippingview et l'UI mais il faut ensuite synchroniser le modèle et la vue en passant par le controleur quand cette dernière est modifiée. C'est ça que je trouve lourd.
    C'est le prix à payer :)
    De toute façon, à partir du moment où tu ne peux pas coller les objets du domaine directement dans l'interface, tu dois bien faire des synchros dans les deux sens à un endroit ou à un autre. Ça peut être en se reposant intégralement sur le databinding, ça peut être tout directement dans une même classe, ou ça peut suivre le principe de MVC, MVP, etc. Il faut le faire quelque part, mais il y a le choix dans la méthode à utiliser :)

    Évidemment si tu peux coller les objets du domaine directement dans l'interface, tu peux simplifier beaucoup de choses. Mais c'est rare de pouvoir le faire... en espérant pouvoir garder le code propre.

    Citation Envoyé par maa Voir le message
    euh tu veux dire que c'est le contrôleur qui va mettre à jour le modèle, non ?
    Nope. Le contrôleur va dire au modèle de travailler un peu. D'ailleurs ça me rappelle que c'est justement un problème avec ta version dans ce post :)
    ShippingController n'a pas à faire model.Days.Add ou model.Days.Remove. C'est au modèle de faire le travail. Le contrôleur est juste là pour dire quoi faire. Ça peut être en conservant le test et en faisant model.AddDay et model.RemoveDay, ou plus simplement en virant le test et en faisant model.ToggleDeliveryDay. Tout le traitement passe du côté du modèle.
    Tu peux aussi faire model.EnableDay et model.DisableDay, et le modèle se chargera non seulement d'ajouter/supprimer le jour, mais aussi de s'assurer qu'il ne peut pas y avoir de doublon ou de suppression d'un jour inexistant. Histoire que si pour une raison ou une autre, tu appelles 2 fois la fonction d'activation du même jour, ça ne te fasse pas de bug sournois. Tout ça, c'est la responsabilité du modèle. Il doit gérer sa propre intégrité. Et le contrôleur n'a en aucun cas à savoir comment est implémenté le modèle.

    C'est notamment une application du principe Tell, Don't Ask. Ce n'est pas au contrôleur de savoir que quand un jour devient possible, il faut l'ajouter à la collection Days du modèle. C'est la cuisine interne du modèle. Le contrôleur a juste besoin de savoir quelle méthode du modèle appeler pour gérer l'action 'un jour est devenu possible/impossible'.

    Citation Envoyé par maa Voir le message
    Je ne vois pas trop ce que tu veux dire par "objet renvoyé par le modèle", ni ce que passer à la vue signifie... Ca voudrait dire que shippingModel serait passé à shippingView par le presenter ? Je ne comprends pas trop le sens.
    Non non, c'est juste ce que tu fais déjà avec Days. C'est histoire que le modèle permette d'obtenir des objets 'du domaine' (comme DeliveryDay). Ils ont leur rôle, leur fonction, et ils sont indépendants de la couche d'interface (et de la couche d'accès aux données aussi, tant qu'à faire).

    Pour la partie 'passer à la vue', c'est juste que dans le cas de MVP/Passive View, c'est le Presenter qui passerait Name, Days etc à la vue (au plus basique, il ferait view.Name = model.Name;)
    Dans cette version-là, la synchro entre vue et modèle se fait dans le presenter. Dans les deux sens.

    En MVC, c'est séparé. Et dans certaines variantes de MVP, où la vue est plus autonome, elle peut aussi se mettre à jour directement depuis le modèle.

    C'est un gros boxon quoi :)

    Citation Envoyé par maa Voir le message
    Sinon il me semble que j'ai bien compris la différence entre MVP et MVC mais je ne sais pas trop lequel est le mieux adapté à mon cas.
    Là comme ça je dirais que c'est un peu au feeling. Il n'y a que toi qui peut choisir. Je peux juste te conseiller de passer par la page GUI Architectures de Martin Fowler vu qu'il décrit très bien les différentes approches. Regarde notamment les diagrammes de chaque pour voir comment se fait la communication et prends celle qui te parle le plus. Essaye de trouver celle avec laquelle tu es le plus confortable.

    Peu importe la variante, à partir du moment où les responsabilités sont séparées, tu y gagnes. Après, chaque approche a ses avantages et inconvénients, et chaque développeur peut avoir plus ou moins d'affinités avec.

    Personnellement j'ai un petit faible pour Passive View, mais ça ne veut pas dire que c'est la méthode la plus appropriée dans la plupart des cas :)

Discussions similaires

  1. Quelques questions sur mon application en pattern MVC
    Par Pavel37 dans le forum Débuter
    Réponses: 0
    Dernier message: 08/03/2013, 11h13
  2. Réponses: 8
    Dernier message: 21/03/2012, 11h00
  3. Quelques questions sur les design pattern
    Par JulienDuSud dans le forum C++
    Réponses: 8
    Dernier message: 22/04/2009, 21h41
  4. Quelques question sur Win 32 Appli
    Par lvdnono dans le forum Windows
    Réponses: 5
    Dernier message: 15/06/2004, 12h37
  5. Quelques questions sur le TWebBrowser...
    Par CorO dans le forum Web & réseau
    Réponses: 3
    Dernier message: 17/01/2003, 21h23

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo