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

JavaScript Discussion :

Utilisation des getter/setter ES5 pour des tests null-safe et de la validation de type à la volée


Sujet :

JavaScript

  1. #1
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut Utilisation des getter/setter ES5 pour des tests null-safe et de la validation de type à la volée
    edit: J'ai continué à travailler sur ces expérimentations pour produire une bibliothèque grand public.
    Après un peu moins de deux mois de taf, voilà enfin le résultat : http://syllab.fr/projets/web/ObjectModel/

    Bonsoir les codeurs,

    J'étais en train de bricoler avec les getters/setters et Object.defineProperty pour voir si je ne pouvais pas m'en servir pour simuler l'API Proxy actuellement à l'étude en ES6 (voir https://developer.mozilla.org/fr/doc..._globaux/Proxy), et je suis arrivé à un truc très sympa.

    A la base, je cherchais à éviter ce genre de tests redondant dans mes codes :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    if(person != null && person.address != null && person.address.work != null && person.address.work.city != null){
        fonctionAvec(person.address.work.city);
    }
    A supposer que l'adresse est un champ faculatatif pour mon utilisateur, dès que je souhaite faire une opération avec un sous-champ bien précis, je dois tester toute la chaîne par prudence pour éviter les exceptions de type Cannot read property 'work' of undefined.

    Les Proxies pourraient permettre de simplifier ce code, en fournissant un objet à la place des valeurs null/undefined si on essaie de faire un getter dessus. Mais leur support est minoritaire et ils ne sont même pas confirmés par le W3C. Il y a aussi les monades pour ça, notamment Maybe, mais cela nous oblige à adapter notre syntaxe un peu partout. Je n'accroche pas beaucoup aux monades, elles font partie de ces choses très simples mais très difficiles à expliquer. Et puis elles ont un nom vraiment moche

    Donc en remplacement des Proxies, on a Object.defineProperty dont on pourrait se servir sur un objet . Le problème est que pour s'en servir, il faut savoir quelle clé définir ! C'est là que j'ai eu l'idée de fournir un "modèle" de données pour les objets que je veux tester. C'est vrai, on peut déclarer quelque-part que le modèle Person est amené à contenir des addresses, dont une professionnelle, avec une ville définie. Même si ces champs sont facultatifs, on les a en tête et on code des instructions avec, alors autant les déclarer quelque-part.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var Person = ObjectModel({ 
      name: String,
      age: Number,
      female: Boolean,
      address: {
        work: {
          city: String
        }
      }
    });
    Tout ce qu'il me fallait, c'était la liste des clés pour pouvoir les déclarer avec defineProperty sur mon objet proxy. Je n'avais pas besoin des valeurs, mais comme je ne savais pas quoi mettre, j'ai mis les types attendus. Et c'est là que m'est venu la seconde idée: faire de la validation de type à la volée. Bon, les erreurs seront remontées au runtime, ce n'est non plus du TypeScript, mais ça peut aider à prévenir certains problèmes de faire valider les types.

    Bref, en quelques heures j'ai réussi à pondre une petite fonction qui fait des miracles :

    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
    function ObjectModel(def) {
     
      function wrap(obj) {
        if (obj === null || obj === undefined) {
          return Object.create(null);
        } else switch (typeof obj) {
          case "string":
            return new String(obj);
          case "number":
            return new Number(obj);
          case "boolean":
            return new Boolean(obj);
          case "object":
          default:
            return obj;
        }
      }
     
      function isPrimitive(def){
        return def === String || def === Number || def === Boolean;
      }
     
      function safeProxy(obj, def) {
        var wrapper = wrap(obj);
        var proxy = Object.create(Object.getPrototypeOf(wrapper));
     
        if(isPrimitive(def)){
          if (obj !== undefined && def(obj) !== obj) {
            throw new Error("expecting " + def.name + ", got " + (typeof obj));
          }
          return obj;
        } else {
          Object.keys(def).forEach(function (key) {
            Object.defineProperty(proxy, key, {
              get: function () {
                return safeProxy(isPrimitive(def[key]) && obj instanceof Object ? obj[key] : wrapper[key], def[key]);
              },
              set: function (val) {
                wrapper[key] = safeProxy(val, def[key]);
              }
            });
          });
        }
     
        return proxy;
      }
     
      return function (obj) {
        return safeProxy(obj, def);
      };
    }
    Cette fonction permet donc de manipuler des objets au rôle de proxy qui vont être mis en parallèle avec un modèle à chaque get/set. Ce qui permet de faire ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    var joe = Person();
    joe.name = "Joe";
    joe.age = "old"; // Error: expecting Number, got string
     
    if(joe.address.work.city){ //joe.address n'est pas défini mais ce code ne lève pas d'exceptions
      //mais cette condition ne passe pas, car joe.address.work.city renvoie undefined
      throw new Error("you should not pass");
    }
    On peut aussi utiliser une syntaxe avec new :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    var ann = new Person;
    ann.female = 1; //Error: expecting Boolean, got number
    ann.age = 25;
    ann.name = joe.name+"'s wife";
    Je cherche à vérifier si l'utilisation de cette fonction présente des problèmes bloquants (notamment sur la pertinence de la distinction objet/primitive). J'ai déjà identifié quelques inconvénients, le principal étant joe.address !== undefined. Je pourrais contourner le problème par une propriété _isDefined mais ce n'est pas aussi élégant que le reste. Vous avez d'autres idées ? J'ai aussi pensé à permettre d'indiquer si le paramètre est facultatif ou non avec la notation age: [Number], pour imiter la syntaxe JSDoc tout en reposant sur les Array pour rester du JS valide. Mais il faut que je trouve autre chose pour indiquer les Array... et quid des Array typées ?

    Si quelques grosses têtes veulent m'aider à creuser le sujet, je fournis les pioches
    One Web to rule them all

  2. #2
    Expert éminent
    Avatar de Watilin
    Homme Profil pro
    En recherche d'emploi
    Inscrit en
    Juin 2010
    Messages
    3 093
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : En recherche d'emploi

    Informations forums :
    Inscription : Juin 2010
    Messages : 3 093
    Points : 6 754
    Points
    6 754
    Par défaut
    J'aime bien l'idée mais il y a un truc qui me dérange :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    typeof Number(42) // "number"
    typeof new Number(42) // "objet"
    En utilisant new sur les types primitifs, tu les transformes en objets au lieu de les caster.

    Ça m'est arrivé de faire de la vérification de type (pour un pretty-printer si ma mémoire est bonne), et la méthode que j'avais choisie c'est de commencer par typeof, et quand ça me renvoie "object" je passe à constructor. Je trouve que la propriété constructor permet assez simplement d'approcher une notion de typage fort. En particulier si on prend soin de toujours nommer ses constructeurs, on peut utiliser constructor.name.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    var tab = [];
     
    typeof tab // "object"
    tab.constructor // function Array()
    tab.constructor.name // "Array"
    Je te remercie de m'avoir fait découvrir les monades À la première lecture j'ai l'impression que c'est comme des promises, l'aspect asynchrone en moins, mais peut-être que je me trompe.
    La FAQ JavaScript – Les cours JavaScript
    Touche F12 = la console → l’outil indispensable pour développer en JavaScript !

  3. #3
    Rédacteur

    Avatar de danielhagnoul
    Homme Profil pro
    Étudiant perpétuel
    Inscrit en
    Février 2009
    Messages
    6 389
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 73
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant perpétuel
    Secteur : Enseignement

    Informations forums :
    Inscription : Février 2009
    Messages : 6 389
    Points : 22 933
    Points
    22 933
    Billets dans le blog
    125
    Par défaut
    Bonsoir

    Je viens de lire les documentations (merci de m'avoir fait découvrir des choses que je ne connaissais pas) et de tester tes codes.

    Le contrôle de la valeur et du typage d'une propriété de l'objet par un accesseur (get) et un mutateur (set) c'est du grand classique. Utiliser une seule fonction pour faire le travail au lieu de devoir le coder pour chacune des propriétés des objets c'est bien. Mais je me demande (je n'ai pas encore testé), que va-t-il se passer si je veux mes propres get/set pour gérer la valeur d'une propriété.

    Pour le reste, je ne suis pas convaincu de l'utilité de la chose. Il me semble que c'est employé un bazooka pour obtenir une valeur "undefined" au lieu de faire de la gestion d'erreur avec un simple try/catch.

    @Watilin: j'ai un peu testé les monades, c'est aussi un bazooka pour un résultat... j'en reviens toujours à un simple try/catch.

    Mon test :

    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
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    $( function(){
     
        try {
     
            var ObjectModel = function Object_Model( def ) {
     
                function wrap( obj ) {
                    if ( obj === null || obj === undefined ) {
                        return Object.create( null );
                    } else {
                        switch ( typeof obj ) {
                            case "string": return new String( obj );
                            case "number": return new Number( obj );
                            case "boolean": return new Boolean( obj );
                            case "object":
                            default: return obj;
                        }
                    }
                }
     
                function isPrimitive( def ) {
                    return def === String || def === Number || def === Boolean;
                }
     
                function safeProxy( obj, def ) {
                    var wrapper = wrap( obj ),
                        proxy = Object.create( Object.getPrototypeOf( wrapper ) );
     
                    if ( isPrimitive( def ) ) {
                        if ( obj !== undefined && def( obj ) !== obj ) {
                            throw new Error( "expecting " + def.name + ", got " + ( typeof obj ) );
                        }
     
                        return obj;
     
                    } else {
                        Object
                            .keys( def )
                            .forEach( function( key ) {
                                Object.defineProperty( proxy, key, {
                                    "get" : function(){
                                        return safeProxy( isPrimitive( def[ key ] ) && 
                                            obj instanceof Object ? obj[ key ] : wrapper[ key ], 
                                            def[ key ] );
                                    },
                                    "set" : function( val ){
                                        wrapper[ key ] = safeProxy( val, def[ key ] );
                                    }
                                });
                            });
                    }
     
                    return proxy;
                }
     
                return function( obj ) {
                    return safeProxy( obj, def );
                };
            };
     
            var Person = ObjectModel({
                "name" : String,
                "age" : Number,
                "female" : Boolean,
                "address" : {
                    "work" : {
                        "city" : String
                    }
                }
            });
     
            console.log( typeof Person ); // function
            console.log( Person ); // function ( obj ) { return safeProxy( obj, def ); }
     
            var joe = Person();
     
            console.log( typeof joe ); // object
            console.log( joe );
     
            /*
             * Object { name: (...), age: (...), female: (...), address: (...) }
             * 
             * address: (...)
             * get address: function (){
             * set address: function ( val ){
             *     
             * age: (...)
             * get age: function (){
             * set age: function ( val ){
             * 
             * female: (...)
             * get female: function (){
             * set female: function ( val ){
             * 
             * name: (...)
             * get name: function (){
             * set name: function ( val ){
             */
     
            joe.name = "Joe";
            joe.age = 24;
     
            console.log( joe.female ); // undefined
            console.log( joe.address.work.city ); // undefined
     
            joe.address = null;
     
            console.log( joe.address.work.city ); // undefined !  
     
        }
        catch( err ){
            console.log( err.message );
        }
     
        try {
     
            var person = {};
     
            person.address = {};
            person.address.work = {};
            person.address.work.city = "Hello";
     
            person.address = null; //"Cannot read property 'work' of null"
     
            person.address = {}; // "Cannot read property 'city' of undefined"
     
            console.log( person.address.work.city );
     
        }
        catch( err ){
            console.log( err.message );
        }
     
     
    });

    Blog

    Sans l'analyse et la conception, la programmation est l'art d'ajouter des bogues à un fichier texte vide.
    (Louis Srygley : Without requirements or design, programming is the art of adding bugs to an empty text file.)

  4. #4
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    @Watilin: en fait, je cherche bien à les transformer et non à les caster (puisque justement je fais de la validation de type). Je me suis servi d'un wrapper sur les primitives pour résoudre mon problème initial, à savoir pouvoir faire ça :
    if(joe.address.work.city){ ... } ; sans risquer d'exceptions "Cannot read property" même si un objet parent vaut null ou est une primitive (par exemple joe.address="1 rue de la paix";). Mais je ne passe pas par les wrappers dans les getters des valeurs qui sont, d'après le modèle, sensées être des primitives. Donc j'ai bien typeof joe.age === "number".
    Le problème avec la propriété constructor est que c'est assez courant de la redéfinir sans faire attention (j'avais pris l'exemple de la classe Car dans un autre topic, il suffit d'un clio.constructor = "Renault" pour potentiellement casser des fonctions internes de bibliothèques/frameworks reposant sur cette propriété. Mieux vaut passer par instanceof à mon avis.

    @danielhagnoul: je dirais plutôt que c'est le try/catch qui est le bazooka Ou plutôt le mur anti-bazooka. L'énorme problème du try/catch, c'est qu'il intercepte tout sans exception. Ce qui fait qu'on doit tester l'erreur et la renvoyer à nouveau si ce n'est pas celle attendue.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    try {
    if(joe.address.work.city){ 
      doSomethingWith(joe.address.work.city);
    } else {
      doSomethingElse();
    }
     
    } catch(e){
      if(e instanceof TypeError){
         doSomethingElse();
      } else {
         throw e; //c'est une autre erreur imprévue, on la renvoie au-dessus
      }
    }
    Outre le fait que ce soit pénible de mettre des try/catch partout et de dupliquer une partie du code dans la branche du catch, c'est également très risqué. Car autant on a prévu à l'avance de potentielles TypeError pour ce if, autant il peut y avoir d'autres TypeError non prévues venant des fonctions doSomethingWith ou doSomethingElse ; non seulement elles ne seront pas remontées en console, mais elles exécuteront un code non prévu.
    One Web to rule them all

  5. #5
    Rédacteur

    Avatar de danielhagnoul
    Homme Profil pro
    Étudiant perpétuel
    Inscrit en
    Février 2009
    Messages
    6 389
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 73
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant perpétuel
    Secteur : Enseignement

    Informations forums :
    Inscription : Février 2009
    Messages : 6 389
    Points : 22 933
    Points
    22 933
    Billets dans le blog
    125
    Par défaut
    Je suis en train de lire flow : A STATIC TYPE CHECKER FOR JAVASCRIPT, malheureusement c'est pour Mac et Linux uniquement.

    Blog

    Sans l'analyse et la conception, la programmation est l'art d'ajouter des bogues à un fichier texte vide.
    (Louis Srygley : Without requirements or design, programming is the art of adding bugs to an empty text file.)

  6. #6
    Membre averti
    Profil pro
    à la bougie alors
    Inscrit en
    Mai 2006
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : à la bougie alors

    Informations forums :
    Inscription : Mai 2006
    Messages : 224
    Points : 362
    Points
    362
    Par défaut
    Bonjour,

    Vous traitez plusieurs choses différentes dans votre code :
    1- l'accés à des données ayant une structure arborescente éventuellement absente pour partie,
    2- le contrôle des informations stockées dans cette structure.

    Pour le deuxième aspect, utiliser les constructeurs est un raccourci interessant mais limité.
    Il est, tel quel, difficile de l'utiliser pour une date. Peut-être que passer par une hiérarchie de classes ou d'objets faciliterait l'intégration de types de données plus ou moins complexes.

    Par exemple :

    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
    function DataType ( type ) {
        if ( typeof type != "undefined" )
            this.type = Object(type);
    };
    DataType.prototype.type  = null;
    DataType.prototype.check = function check ( value ) { return false; };
     
    function PrimitiveType ( type ) {
        DataType.call(this, type);
        if ( typeof type == "function" )
            this.name = type.name;
    };
    PrimitiveType.prototype = new DataType();
    PrimitiveType.prototype.name  = "PrimitiveType";
     
    PrimitiveType.prototype.check = function check ( value ) {
        return this.type(value) === value;
    };
     
    var StringType  = new PrimitiveType(String);
    var BooleanType = new PrimitiveType(Boolean);
    var NumberType  = new PrimitiveType(Number);
     
    var Person = ObjectModel({ 
      name: StringType,
      age: NumberType,
      female: BooleanType,
      // ...
    });
    En changeant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    function isPrimitive(def) {
        return def === String || def === Number || def === Boolean;
    }
    Par

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    function isDataType(def) {
        return def instanceof DataType;
    }
    Et

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
          if ( isPrimitive(def) ) {
                if ( obj !== undefined && def(obj) !== obj ) {
                    throw new Error("expecting " + def.name + ", got " + (typeof obj));
                }
                return obj;
            }
    Par

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
          if ( isDataType(def) ) {
                if ( obj !== undefined && ! def.check(obj) ) {
                    throw new Error("expecting " + def.name + ", got " + (typeof obj));
                }
                return obj;
            }
    Je parlais de date, parce que je m'attendais plutot à avoir un champ 'birthdate' représentant une date donc et un champ 'age' qui serait un champ calculé.
    Une question est : est-ce que les champs calculés font partie de la structure de données ou pas ?
    J'ai supposé que non. Dans ce cas, on peut l'inclure dans le prototype de la classe 'Person' ce qui implique de petits changements :

    Rajouter le type correspondant à la date

    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
    function ObjectType ( type, name ) {
        DataType.call(this, type);
        if ( arguments.length < 2 ) {
            if ( typeof type == "function" )
                this.name = type.name;
        } else {
            this.name = String(name);
        }
    };
    ObjectType.prototype = new DataType();
    ObjectType.prototype.name = "Object";
     
    var DateTimeType = new ObjectType(Date);
    DateTimeType.check = function check ( value ) {
        if ( value == null )
            return false;
        if ( value instanceof Date )
            return true;
        return /^((\d{1,2})\/(\d{1,2})\/(\d{4}))?\s*((\d{1,2}):(\d{2}):(\d{2}))?$/.exec(String(value));
    };
    NB : le type décrit n'est pas bon, dans la mesure où ce qui est réellement stocké peut-être un objet Date ou une chaine de caractère. Il faudrait étendre DataType avec une méthode de normalisation de la valeur stockée.

    S'assurer que dans 'ObjectModel', la fonction se comportera comme un constructeur

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
      return function (obj) {
            return safeProxy(obj, def);
        };
    serait remplacé par

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
      return function (obj) {
            var Ctor = arguments.callee;
            if ( this instanceof Ctor )
                return safeProxy(this, def);
            if ( obj instanceof Ctor )
                return safeProxy(obj, def);
            return new Ctor(); //?
        };
    'Person' deviendrait

    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
    var Person = ObjectModel({ 
      name: StringType,
      birthdate: DateTimeType,
      female: BooleanType,
      address: {
        length: NumberType,
        work: {
          city: StringType
        }
      }
    });
    Object.defineProperty(Person.prototype,"age",{
        get:function(){// un peu à l'arrache ...
            if ( this.birthdate )
                return (new Date()).getUTCFullYear()
                       - (this.birthdate instanceof Date 
                          ? this.birthdate 
                          : new Date(String(this.birthdate)) ).getUTCFullYear();
            return undefined;
        }
    });
    Pour finir, 'joe' devient

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    var joe = new Person();
    joe.name = "Joe";
    joe.birthdate = "1/12/2000";
    // joe.age == 14
    Pour le premier point, l'accés à une partie de structure absente, là encore la solution est plutot interessante. Mais je me suis fait piégé par :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    joe.address.work.city = "limit";
    l'affectation ne fonctionne pas. Ce qui est logique mais qui m'a fait me poser la question de l'utilisation de la structure de données.

    Comment affecte-t-on une valeur à 'joe.address.work.city' ?

    Quand à savoir, si une partie de l'arborescence est valide, peut-être que l'ajout d'un propriété 'undefined' à 'true' pourrait faire l'affaire.
    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
      function wrap(obj) {
            if (obj === null || obj === undefined) {
                obj = Object.create(null);
                obj.undefined = true;
                return obj;
            } else switch (typeof obj) {
                case "string":
                    return new String(obj);
                case "number":
                    return new Number(obj);
                case "boolean":
                    return new Boolean(obj);
                default:
                    return obj;
            }
        }
    Cela permettrait de tester

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    if ( jon.address.undefined )
        // ...
    Puisque lors de l'affectation, 'address' serait remplacé par un objet n'ayant pas cette propriété 'undefined' (ou 'isUndefined' ou ...)

    Enfin, parce que je m'amuse plutot en ES3, j'aurais utilisé soit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Ø = {}; // Ø = Object.freeze(Object.create(null));
     
    if ( (((jon || Ø).address || Ø).work || Ø).city != null )
        // ...
    Soit

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    if ( getValue(joe,"address.work.city") != null )
        // ...
    En espérant que ces quelques remarques contribueront à votre interessante reflexion.

  7. #7
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    Oui j'ai continué à creuser le sujet de mon côté et j'ai suivi les mêmes pistes que toi lysandro. J'ai fait plusieurs modifications sur le code qui rejoignent un peu ce que tu proposes, je posterai une mise à jour ce soir.

    En fait ma distinction isPrimitive n'a pas de sens car je voudrais permettre de gérer toutes sortes de classes et de faire de la composition, par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    var Family = Object.defineModel({  father: Person,
      mother: Person
    })

    En considérant que les modèles sont des arbres, et qu'on valide uniquement le type des "feuilles", je me suis dit qu'on pourrait simplement identifier une feuille par le fait que ce soit une fonction. La POO en JavaScript repose actuellement énormément sur les fonctions constructeurs, et je passe déjà des fonctions pour identifier les primitives (String, Number, Boolean). Je n'ai pas encore vérifié si cette approche couvrait tous les cas (notamment par rapport aux champs calculés)
    One Web to rule them all

  8. #8
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    Okay j'ai bien avancé et j'ai ajouté plein de trucs sympas

    Liste de types autorisés
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var Person = Object.defineModel({
      name: String,
      age: [Number, Date] 
    });
     
    var joe = Person({ name: "Joe", age: 42 });
    joe.age = 43;
    joe.age = new Date(1985,1,14);
    joe.age = "44"; //Uncaught Error: expecting age to be Number or Date, got string
    Paramètres optionnels, même syntaxe que la JSDoc
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    var Person = Object.defineModel({
      name: String,
      age: [Number] /* équivalent à [Number, undefined] */
    });
     
    var joe = Person();
    console.log(joe.age); //undefined
    console.log(joe.name); //Uncaught Error: expecting name to be String, got undefined
    Liste de valeurs autorisées
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    var Dalton = Object.defineModel({
      name: ["Joe","William","Jack","Averell"]
    });
    var joe = Dalton({ name: "Joe" });
    var lucky = Dalton({ name: "Luke" });
    console.log(joe.name); //"Joe"
    console.log(lucky.name); //Uncaught Error: expecting name to be Joe or William or Jack or Averell, got string
    Composition de modèles
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    var Family = Object.defineModel({
      father: Person,
      mother: Person
    });
     
    var joefamily = Family({ father: joe, mother: ann });
    joefamily.mother = "Ann"; //Error: expecting mother to be {name: String, age: [Number, Date]}, got string
    Listes typées
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var Family = Object.defineModel({
      father: Person,
      mother: Person,
      children: Array.defineModel(Person),
      grandparents: [Array.defineModel(Person).max(4)]
    });
     
    var joefamily = new Family({
      father: joe,
      mother: ann
    });
     
    joefamily.children = [Person({ name: "Jack" }), Person({ name: "Jim" }), "John"]; //Error: expecting Array[2] to be {	name: String,age: [Number, Date]}, got string
    Listes limitées en taille (minimum, maximum)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    joefamily.grandparents = [Person({ name: "Jack" }), Person({ name: "Jim" }), Person({ name: "Jane" }), Person({ name: "Jilian" }), Person({ name: "Juliet" })]; // Uncaught Error: expecting grandparents to be Array.of({name: String,age: [Number, Date]}) (between 0 and 4 items) or undefined, got object
    Et j'ai encore plein d'autres idées en tête ! Le mieux avec tout ça est que le code n'a pas énormément augmenté, on est toujours de l'ordre de la centaine de lignes en comptant de grosses fonctions de log/debug.

    Voilà le code et une page de test :
    http://syllab.fr/projets/experiments/ObjectDefinition/
    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
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    (function(){
      function isFunction(o){
        return typeof o === "function";
      }
     
      function isTyped(def){
        return isFunction(def) || Array.isArray(def) || def instanceof ArrayModel;
      }
     
      function wrapObject(obj) {
        return obj instanceof Object ? obj : Object.create(null);
      }
     
      function validateType(obj, _def, path){
        var def = _def;
        if(!Array.isArray(_def)) {
          def = [_def];
        } else if(def.length < 2){
          def = _def.concat(undefined);
        }
     
        if (!def.some(function(type){
             if(obj == null){ return obj == type; }
             if(type instanceof ArrayModel){ return type.validate(obj); }
             return obj == type || (type instanceof Function && obj instanceof type) || obj.constructor === type;
           })) {
          var authorizedTypes = def.map(function(t){ return isFunction(t) ? t.name || t.toString() : String(t) });
          throw new Error("expecting " + path + " to be " + authorizedTypes.join(" or ") + ", got " + (typeof obj));
        }
      }
     
      function objToString(obj, ndeep){
        if(ndeep === undefined){ ndeep = 1; }
        if(obj == null){ return String(obj); }
        if(typeof obj == "string"){ return '"'+obj+'"'; }
        if(typeof obj == "function"){ return obj.name || obj.toString(ndeep); }
        if(Array.isArray(obj)){
          return '[' + obj.map(function(item) {
               return objToString(item, ndeep+1);
             }).join(', ') + ']';
        }
        if(obj && typeof obj == "object"){
          var indent = Array(ndeep).join('\t');
          return '{' + Object.keys(obj).map(function(key){
               return '\n\t' + indent + key + ': ' + objToString(obj[key], ndeep+1);
             }).join(',') + '\n' + indent + '}';
        }
        return String(obj);
      }
     
      Object.defineModel = function(modelDefinition) {
     
        function getProxy(obj, def, path) {
          var wrapper = wrapObject(obj);
     
          if(isTyped(def)){
            validateType(obj, def, path);
            return obj;
          } else {
            var proxy = Object.create(Object.getPrototypeOf(wrapper));
            Object.keys(def).forEach(function(key) {
              var newPath = (path ? [path,key].join('.') : key);
              Object.defineProperty(proxy, key, {
                get: function () {
                  //console.log("get",key, def[key]);
                  return getProxy(wrapper[key], def[key], newPath);
                },
                set: function (val) {
                  //console.log("set",key, def[key], val);
                  wrapper[key] = getProxy(val, def[key], key, newPath);
                }
              });
            });
            Object.defineProperty(proxy,"constructor",{ //TODO: trouver une solution moins intrusive pour tester isType(obj, Model)
              configurable: false,
              writable: false,
              value: constructor
            });
            return proxy;
          }
        }
     
        var constructor = function(obj) {
          return getProxy(obj, modelDefinition);
        };
        constructor.toString = objToString.bind(this, modelDefinition);
        return constructor;
      };
     
      function ArrayModel(def){
        this.def = def;
        this._min = 0;
        this._max = Infinity;
      }
      ArrayModel.prototype.min = function(val){ this._min = val; return this; };
      ArrayModel.prototype.max = function(val){ this._max = val; return this; };
      ArrayModel.prototype.validate = function(obj){
        var type = this.def;
        return obj.length >= this._min && obj.length <= this._max
           && obj.every(function(o,i){ validateType(o, type, 'Array['+i+']'); return true; });
      };
      ArrayModel.prototype.toString = function(ndeep){
        return 'Array.of('+objToString(this.def, ndeep)+') (between '+this._min+' and '+this._max+' items)';
      };
     
      Array.defineModel = function(def){
        return new ArrayModel(def);
      };
     
    })();
    Il doit sûrement y avoir un paquet de bugs, et je dois valider les instances de modèles dès leur création. Mais à part ça, j'ai comme l'impression qu'il y a moyen de faire une petite librairie bien sympathique avec tout ça.
    One Web to rule them all

  9. #9
    Rédacteur

    Avatar de danielhagnoul
    Homme Profil pro
    Étudiant perpétuel
    Inscrit en
    Février 2009
    Messages
    6 389
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 73
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant perpétuel
    Secteur : Enseignement

    Informations forums :
    Inscription : Février 2009
    Messages : 6 389
    Points : 22 933
    Points
    22 933
    Billets dans le blog
    125

    Blog

    Sans l'analyse et la conception, la programmation est l'art d'ajouter des bogues à un fichier texte vide.
    (Louis Srygley : Without requirements or design, programming is the art of adding bugs to an empty text file.)

  10. #10
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    J'utilise habituellement Object.prototype.toString , étant donné qu'il est plus précis en ce qui concerne les objets prédéfinis par le navigateur (Array, Date...). Mais pour tester les types de base, typeof fait l'affaire.

    Là où je suis embêté, c'est pour assigner le constructeur aux objets proxys que je crée (voir commentaire TODO dans le code). Je ne peux pas toucher au prototype car il doit être celui de l'objet initial, et je ne peux pas non plus modifier cette propriété interne [[Class]]. Le seul truc que j'ai trouvé est de lui assigner directement en propriété (donc tant qu'à faire, j'ai pris constructor). Mais je n'aime pas cette solution car je la trouve vulnérable (voiture.constructor = "Ford";). Mettre des noms de propriétés tordus comme __constructor__ ne résout pas non plus le problème, ça l'esquive seulement. Les Proxy ES6 me seraient très utiles ici. Vous voyez une autre solution en ES5 ?
    One Web to rule them all

  11. #11
    Rédacteur

    Avatar de danielhagnoul
    Homme Profil pro
    Étudiant perpétuel
    Inscrit en
    Février 2009
    Messages
    6 389
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 73
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant perpétuel
    Secteur : Enseignement

    Informations forums :
    Inscription : Février 2009
    Messages : 6 389
    Points : 22 933
    Points
    22 933
    Billets dans le blog
    125

    Blog

    Sans l'analyse et la conception, la programmation est l'art d'ajouter des bogues à un fichier texte vide.
    (Louis Srygley : Without requirements or design, programming is the art of adding bugs to an empty text file.)

  12. #12
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    Donc pas d'autre solution que d'ajouter une propriété à l'objet ? Je comptais encore chercher du côté de instanceof.
    One Web to rule them all

  13. #13
    Membre averti
    Profil pro
    à la bougie alors
    Inscrit en
    Mai 2006
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : à la bougie alors

    Informations forums :
    Inscription : Mai 2006
    Messages : 224
    Points : 362
    Points
    362
    Par défaut
    Je n'ai pas fini de comprendre le code mais quelques remarques tout de même :

    1- Pour quelqu'un qui voulait éviter d'avoir des erreurs en parcourant une arborescence, tu te retrouve avec des erreurs de type lors de la lecture de propriétés typées ... je me demande si c'est ce que tu voulais ?

    2- Dans ton exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    var Dalton = Object.defineModel({
      name: ["Joe","William","Jack","Averell"]
    });
    var lucky = Dalton({ name: "Luke" });
    console.log(lucky.name); //Uncaught Error: expecting name to be Joe or William or Jack or Averell, got string
    ça me gène : ça devrait planter sur l'encapsulation ('lucky = Dalton({ name: "Luke" })') de l'objet et pas sur la lecture de la propriété. Je comprends pourquoi mais sans connaitre l'implémentation, cela ressemble à une création ou un transtypage et donc on s'attend à ce que les erreurs soient déclenchées à ce moment là et non plus tard lors d'une éventuelle lecture d'une propriété.

    3- La fonction renvoyée par 'Object.defineModel' n'est pas un constructeur (dans le sens où elle n'utilise pas 'this'), ce n'est pas nécessairement génant mais par contre tu l'utilise parfois avec 'new' : 'ann = new Person', 'joefamilly = new Familly', il me semble qu'il y a là une confusion (ou j'ai raté quelque chose ?).

    4- Maintenant, pour la question du test de type :
    si Object.defineModel renvoyait un 'vrai' constructeur, ton problème serait résolu (je crois)
    - tu aurais un objet du bon type ,
    - l'objet passé serait un initialiseur, du coup il y aurait affectation et donc plus de remarque 2-

    L'inconvénient, c'est qu'aujourd'hui c'est un wrapper utilisable par exemple à partir de données JSON sans copie de ces données (mais avec qd mm de la création d'objet)

    PS: le problème de Object.prototype.toString pour déterminer le type, c'est Object.prototype.toString.call(Array.prototype) == "[object Array]" donc à manipuler avec précautions (pas spécifique à Array).

  14. #14
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    1- tu peux préciser les cas ? en fait je distingue dans l'arborescence les branches des feuilles. Seules les feuilles sont typées, donc name, age, city ont tous le bon type. Par contre joe.address renverra un objet même s'il est undefined, car j'ai mis ce comportement pour répondre à mon besoin originel (parcours null-safe). Mais il suffit de retirer le wrapObject et de faire quelques autres petites modifs pour avoir tous les noeuds typés.

    2- oui comme dit en fin de post, je dois valider le modèle à l'initialisation, c'est un bug à régler.

    3- je n'aime pas l'opérateur new personnellement (voir ici), mais j'ai fait en sorte ici que l'utiliser avec ou sans new ne change rien. D'ailleurs je n'utilise pas de this dans le code à part pour les ArrayModel.

    4- qu'est-ce que tu appelles un "vrai" constructeur ? Je pense avoir une petite idée de ce que tu essaies de me faire comprendre, mais j'ai du mal à voir les changements que ça implique dans le code.
    One Web to rule them all

  15. #15
    Membre averti
    Profil pro
    à la bougie alors
    Inscrit en
    Mai 2006
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : à la bougie alors

    Informations forums :
    Inscription : Mai 2006
    Messages : 224
    Points : 362
    Points
    362
    Par défaut
    1- exemple
    Citation Envoyé par SylvainPV Voir le message
    A la base, je cherchais à éviter ce genre de tests redondant dans mes codes :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    if(person != null && person.address != null && person.address.work != null && person.address.work.city != null){
        fonctionAvec(person.address.work.city);
    }
    A supposer que l'adresse est un champ faculatatif pour mon utilisateur, dès que je souhaite faire une opération avec un sous-champ bien précis, je dois tester toute la chaîne par prudence pour éviter les exceptions de type Cannot read property 'work' of undefined.
    maintenant avec
    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
    var Person = Object.defineModel({
      name: String,
      age: [Number],
      address: {
        work: {
          city: String
        }
      }
    });
    var joe = Person({
      name: "Joe",
      age: 42
    });
    if ( joe.address.work.city == null )
      ...
    On a droit à Error: expecting address.work.city to be String, got undefined. ça m'a amusé d'où ma remarque.

    3- Oui, je comprends malheureusement j'ai une position à peu prés inverse et je suis assez réservé sur les positions de D. Crockford mais ce n'est pas le sujet.

    4- J'hésite encore car je n'ai pas assimilé dans le détail ton code et je ne suis pas sur de ne pas dire une connerie mais l'idée est à peu prés celle de mon dernier "post", renvoyer un truc du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    var Constructor = function Constructor ( object_initializer ) {
      if ( this instanceof Constructor ) { // (1)
        return getProxy(this,def,object_initializer); // (2)
      } else { // constructeur appelé comme fonction
        return Constructor.apply(Object.create(Constructor.prototype), arguments);
      }
    }
    (1): le contexte d'appel est soit avec l'opérateur new donc c'est ok, soit le constructeur est appelé comme méthode d'une instance déjà existante. a priori peu de chance que ça se produise mais même dans ce cas, au pire on "re-construit" l'objet. il faut voir pour les effets de bord mais cela me semble gérable.
    (2): comme getProxy retourne un objet qui hérite de son premier paramètre si c'est un objet, on se retrouve avec getProxy(this,...) instanceof Constructor /* Person par exemple */(c'est une version simple juste pour illustrer)
    Encore une fois, je ne suis pas sur que ce soit une solution, c'est juste une idée. Je ne maitrise pas suffisement le détail de ton code.

  16. #16
    Membre averti
    Profil pro
    à la bougie alors
    Inscrit en
    Mai 2006
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : à la bougie alors

    Informations forums :
    Inscription : Mai 2006
    Messages : 224
    Points : 362
    Points
    362
    Par défaut
    1- en fait, il faudrait pour correspondre à ton cas que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    var Person = Object.defineModel({
      ...
      address:{
        work:[Address] // type facultatif
      }
      ...
    });
    avec

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    var Address = Object.defineModel({
      city:String // type obligatoire
    });
    Et dans ce cas, parce que le bloc est Address est facultatif dans Person, l'accés à ...city ne devrait pas générer d'erreurs.

  17. #17
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    Ah oui j'ai rajouté la gestion des paramètres facultatifs et obligatoires après avoir écrit cet exemple, et je ne l'ai pas mis à jour depuis. Mais ça fonctionne comme prévu

    Ta solution fonctionne ! Je me suis emmêlé les pinceaux, je cherchais à résoudre le problème depuis l'objet proxy alors que la solution était dans la déclaration du constructeur.
    J'ai dû modifier le code comme ceci pour que ça fonctionne:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     var Constructor = function( object_initializer ) {
      if(!(this instanceof Constructor)){
        return new Constructor( object_initializer );
      }
      for(var prop in object_initializer){
        if(object_initializer.hasOwnProperty(prop)){
          this[prop] = object_initializer[prop];
        }
      }
      return getProxy(this, modelDefinition,object_initializer);
    };
    La copie des propriétés semble être une étape obligatoire, mais ce n'est pas très grave. A présent on a bien: Person() instanceof Person, les choses redeviennent dans l'ordre ! Merci beaucoup
    One Web to rule them all

  18. #18
    Membre averti
    Profil pro
    à la bougie alors
    Inscrit en
    Mai 2006
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : à la bougie alors

    Informations forums :
    Inscription : Mai 2006
    Messages : 224
    Points : 362
    Points
    362
    Par défaut
    Eh bien, comme rien n'est jamais simple dans la vie :

    Il y a un "post" (je ne sais plus lequel) de quelqu'un qui changeait ses prototypes à la volée parce qu'il gérait des données graphiques en nombre (je crois), qu'il recevait en JSON. Et la création de nouveaux objets pour implémenter les méthodes était trop gourmande en ressources (un truc comme ça).
    Donc, le fait d'utiliser l'objet passé directement peut aussi être intéressant, en terme d'économie mémoire, mais aussi en terme d'unicité des données.
    Le problème que cela soulève, c'est que l'objet en question peut-être modifié en dehors de sa "classe" modèle.

    De l'autre coté, ne pas l'utiliser permet d'avoir effectivement la relation <proxy> instanceof <Class> mais oblige du coup à dupliquer les propriétés. Et à mettre à jour, si ça a un sens, l'objet d'origine.
    (d'ailleurs, une copie en profondeur, si tu continue d'utiliser une arborescence serait une bonne idée.)

    Personellement, je ne me prononce pas pour telle ou telle approche , les deux ont leurs avantages et inconvénients.
    A moins qu'il soit possible de les méler ?

  19. #19
    Rédacteur/Modérateur

    Avatar de SylvainPV
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    3 375
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2012
    Messages : 3 375
    Points : 9 944
    Points
    9 944
    Par défaut
    Je n'ai pas trouvé de solution pour utiliser directement object_initializer, et à y réfléchir, je crois que c'est mieux ainsi. L'utilisation d'un objet pour décrire le modèle a un but uniquement visuel, tout comme les autres syntaxes n'ont de sens que visuellement au moment de la déclaration (utiliser les Array pour décrire les paramètres optionnels par exemple). Je pourrais ajouter une méthode getModel() qui renvoie cet objet originel, mais je ne vois pas d'utilité pratique à ça. Le développeur peut déjà avoir un rappel descriptif du modèle avec la méthode toString, en rentrant simplement "Person" en console.

    J'ai ajouté la validation à l'initialisation, et corrigé quelques détails. Je pense ajouter des tests unitaires et en faire une petite lib. Si ça mène à quelque-chose de viable, je la publierai.
    Si vous voyez d'autres cas d'utilisation possibles aux proxy à base de getter/setter, outre la validation de type et le data-binding, ça m'intéresse.
    One Web to rule them all

  20. #20
    Membre averti
    Profil pro
    à la bougie alors
    Inscrit en
    Mai 2006
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : à la bougie alors

    Informations forums :
    Inscription : Mai 2006
    Messages : 224
    Points : 362
    Points
    362
    Par défaut
    Pour la validation à l'initialisation, j'avais pensé que tu ferais, à partir de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var Constructor = function( object_initializer ) {
      if(!(this instanceof Constructor)){
        return new Constructor( object_initializer );
      }
      for(var prop in object_initializer){
        if(object_initializer.hasOwnProperty(prop)){
          this[prop] = object_initializer[prop];
        }
      }
      return getProxy(this, modelDefinition,object_initializer);
    };
    simplement quelque chose comme

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var Constructor = function( object_initializer ) {
      if(!(this instanceof Constructor)){
        return new Constructor( object_initializer );
      }
      var proxy = getProxy(this, modelDefinition,object_initializer);
      for(var prop in object_initializer){
        if(object_initializer.hasOwnProperty(prop)){
          proxy[prop] = object_initializer[prop];
        }
      }
      return proxy;
    };
    puisque proxy est justement l'objet au travers duquel on fait le controle de type.
    (il vaudrait mieux une copie en profondeur, mais ça dépend du code dans getProxy())

    Ton principe me fait penser aux Model utilisé dans swing ainsi qu'au principe des wrappers.
    Là, ce qui m'intéresse, c'est l'idée d'avoir une structure de données sous-jacente, à laquelle on ajoute des fonctionnalités comme le controle du type, le parcours null-safe, etc tout en ne travaillant que sur la structure de données d'origine ou une structure de données de base non typée s'il faut la créer.
    J'ai récupéré ton code sur ta page de test, si j'ai le temps j'essaierai de voir s'il est possible de faire ce à quoi je pense et je te le soumettrai.
    edit: si tu es d'accord évidemment

Discussions similaires

  1. Utilisation de l'api criteria pour des join
    Par hugo123 dans le forum JPA
    Réponses: 0
    Dernier message: 04/02/2011, 11h46
  2. Réponses: 1
    Dernier message: 31/12/2009, 14h31
  3. Inlining des getters / setters auto avec GCC?
    Par _skip dans le forum Débuter
    Réponses: 45
    Dernier message: 17/08/2009, 12h51
  4. [Template] Changer la génération des getter/setter
    Par anthyme dans le forum NetBeans
    Réponses: 2
    Dernier message: 05/07/2007, 09h26
  5. Réponses: 1
    Dernier message: 31/05/2007, 11h57

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