Forum Programmation.web Question sur les factories en Js

Posté par . Licence CC by-sa
2
30
mar.
2016

Bonjour,
Attention : DÉBUTANT en Js.

Je me mets au Js, côté serveur (donc avec Node). L'envie d'apprendre un nouveau et avec lequel je peux tout faire si j'en ai envie, du script shell au web côté client. Et comme je viens de la vieille école (C++/C#), j'essaie de faire les choses bien.

Parmi la pléthore de moyens pour créer des objets en Js, j'ai choisi d'utiliser la notation littérale et les "factories" pour créer les instances. Ca donne donc un truc comme ça :

// ES6 only
'use strict';

// The base object, "object literal" syntax
let animal2 = {
  // public member
  animalType: 'animal',

  // public method 
  describe() {
    return `This is "${this.animalType}"`;
  }
};

// factory function which serves 2 purposes:
// - encapsulation, that's where private things are declared thanks to closures
// - the "real" object creation, this prevents to use "new" which is not really Js-ish
let afc = function afc() {
  // private member
  let priv = "secret from afc";

  return Object.create(animal2, {
    // Object customisation
    animalType: { value: 'animal with create'},    

    // object extension. The new objects created here get 3 new members:
    // - a private member
    // - a new property
    // - a new method to access the private member

    // new public member
    color: { value: 'green' },
    secret: {
      get: function () { return priv; },
      set: function (value) { priv = value; },
    },
    KO1() {
      console.log("KO1");
    },
    KO2: function() {
      console.log("KO2");
    }
  });
}

// creation of an animal instance
let tac = afc();

Désolé si le code est en Anglais, je fais ça depuis 20 ans, et j'ai pas envie de changer !

Ce code fonctionne plutôt pas mal. Cependant je n'arrive pas appréhender la syntaxe pour écrire une méthode qui soit autre chose qu'un accesseur, mais qui permette néanmoins la manipulation de membres privés. Je mets ici 2 exemples de syntaxe qui me viennent à l'esprit, mais quand je lance un tel code avec Node, j'obtiens "KOx is not a function".

J'apprécie tous les conseils, bonnes pratiques, etc … et une proposition pour implémenter cette satanée fonction.

Merci.

  • # sans prétentions

    Posté par . Évalué à 2.

    J'ai bien peur de ne pouvoir te renseigner sur les bonnes pratiques… je doutes de ne pas dire plus de bêtises qu'autre chose, et je ne crée plus que très rarement des classes en js.

    Ceci dit, je propose cette syntaxe,

    function Animal () {
      this.race = 'cheval'
      this.cri = 'hheeeinnnn'
      this.describe = function () {return 'whatever';}
      console.log('   ctor animal')
    }
    Animal.prototype.what = 'ever';
    
    function Animal2 () {
      Animal.call(this);
      console.log('   ctor animal2')
      this.tomate = function(){}
    }
    // Dans le browser
    Animal2.prototype = Object.create(Animal.prototype); // pareil que: Animal2 extends Animal
    // Avec node, 
    // require('util').inherits(Animal2, Animal); // pareil que: Animal2 extends Animal
    Animal2.prototype.some = 'other';
    
    function animalFactory (which) {
    
      var maBete;
      if (which==='chat') {
        maBete = new Animal2();
        maBete.race = 'chat'
        maBete.cri = 'miaou'
        var privateSecret = 'MIIIAAAAAOOOOUUUU'
        maBete.passCode = function () {
          return privateSecret;
        }
      } else {
          maBete = new Animal();
      }
    
      return maBete;
    }
    
    var chat = animalFactory('chat')
    var cheval = animalFactory('cheval')
    
    console.log('');
    console.log('chat est un animal: %s', chat instanceof Animal);
    console.log('cheval est un animal: %s', cheval instanceof Animal);
    
    console.log('');
    console.log('chat est un animal2: %s', chat instanceof Animal2);
    console.log('cheval est un animal2: %s', cheval instanceof Animal2);
    
    console.log('');
    console.log('chat %j', chat);
    console.log('cheval %j', cheval);
    
    console.log('');
    console.log('chat properties');
    for (var n in chat) {
      console.log('%s : %s', n, chat[n])
    } // note l'affichage des propriétés 'some' and 'what'
    
    console.log('');
    console.log('chat.keys() %j', Object.keys(chat))
    
    
    console.log('');
    console.log('cheval properties');
    for (var n in cheval) {
      console.log('%s %s', n, cheval[n])
    } // note l'affichage de la propriété 'what'
    
    console.log('');
    console.log('cheval.keys() %j', Object.keys(cheval))

    tldr; ma préférence à moi
    - pas d'Object.create,
    - si je dois faire un getter, je fais une variable définie dans un scope et une méthode pour y accéder
    - si je veux faire une propriété publique, je fais une propriété d'objet. Si je venais à avoir des problèmes et que j'avais soudainement le besoin de capturer le set/get, je me tournerais vers Object.defineProperty

    Voir,
    - https://nodejs.org/docs/latest/api/util.html#util_util_inherits_constructor_superconstructor
    - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create

    Finalement, je n'aurais pas utiliser es6. C'est mon gout comme c'est ton choix, mais c'est la mode pas comme evelyne thomas.

    • [^] # Re: sans prétentions

      Posté par . Évalué à 1.

      J'oubliais, pour connaitre le passCode de chacun de ces animaux,

      console.log('chat.passCode() %j', chat.passCode && chat.passCode())
      
      
      console.log('cheval.passCode() %j', cheval.passCode && cheval.passCode())

      et non cela n'affichera pas un booléen, et c'est très bien !

    • [^] # Re: sans prétentions

      Posté par . Évalué à 1.

      Merci pour ta proposition.

      ES6 pour moi, c'est pas une question de mode, mais comme je débute, autant prendre le dernier standard.

      La syntaxe par fonction constructeur, j'aime moyen, surtout parce qu'elle oblige à utiliser "new" qui me semble en dehors de clous de la philosophie Js, mais je peux me tromper hein ;-)

      Mon but n'est d'ailleurs pas de créer des hiérarchie d'objets, je suis assez partisan de "l'héritage c'est le mal". Mais simplement de pouvoir créer des instances, avec de l'encapsulation public/privé.

      Cela dit, je vois comment tu fais le truc, et ça me donne l'idée d'ajouter les fonctions autres que les accesseurs dans la factory, juste après la création de l'instance. C'est pas beau, mais ça devrait marcher.

      Bancal ce langage quand même…

      • [^] # Re: sans prétentions

        Posté par . Évalué à 0.

        La syntaxe par fonction constructeur, j'aime moyen, surtout parce qu'elle oblige à utiliser "new" qui me semble en dehors de clous de la philosophie Js, mais je peux me tromper hein ;-)

        c'est un outil du langage à toi de de le comprendre et de t'en servir au mieux,

        quelques exemples d'utilisation de new,
        - https://github.com/feross/webtorrent/blob/master/index.js
        - https://github.com/watson/bonjour/blob/master/index.js

        Le contre exemple qui va bien
        - https://github.com/mafintosh/multicast-dns/blob/master/index.js

        dogmatism is evil ;)

        Bancal ce langage quand même…

        Il y a un cap à passer sur ce sujet des classes et constructeurs.
        Après cela, le langage reste bancal :D
        Mais comme il donne des possibilités sans toute la lourdeur d'un langage hiérarchisé, j'apprécie.

      • [^] # Re: sans prétentions

        Posté par . Évalué à 1.

        Pour revenir sur es6, l'un ou l'autre importe peu, par contre, ton utilisation de la fonction fléchée KO1, me laisse perplexe.

        Faudrait le tester mais je soupçonne que le this de KO1 === afc et non ta bête…. A confirmer !

        https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions/Fonctions_fl%C3%A9ch%C3%A9es

        bref, es6, mon avis très personnel, ça met encore plus de souk là où c'était déjà un joyeux désordre.

        • [^] # Re: sans prétentions

          Posté par . Évalué à 1.

          Oulà, je crois être encore loin de maitriser les fonctions fléchées. Et mes KOx n'en sont pas (ou alors j'ai raté un truc), juste 2 exemples d'écrire les fonctions qui me semblaient correctes…et qui ne le sont apparemment pas.

          • [^] # Re: sans prétentions

            Posté par . Évalué à 0.

            et bien justement, il se trouve que tu as écris une fonction fléchée !

            regardes ton code initial,

                KO1() {
                  console.log("KO1");
                },
                KO2: function() {
                  console.log("KO2");
                }
            

            KO1 est une fonction fléchée ! Enfin, ça y ressemble imho.

            Le truc étant que comme ni Object.create, ni l'objet d'extension {animalType…} ne définisse de scope, je suppute que le this sera affecté a afc, mais cela reste à vérifier.

            Ceci dit, depuis le début ta syntaxe de KO1 me pose un souci.

            Essaies de lire ces papiers, ils m'ont l'air plus appropriés à ce que tu souhaites écrire

            class Carré extends Polygone {
              constructor(longueur) {
                // On utilise le constructeur de la classe parente
                // avec le mot-clé super
                super(longueur, longueur);
                // Pour les classes dérivées, super() doit être appelé avant de
                // pouvoir utiliser 'this' sinon cela provoque une exception
                // ReferenceError
                this.nom = 'Carré';
              }
            
              get aire() {
                return this.hauteur * this.largeur;
              }
            
              set aire(valeur) {
                this.aire = valeur;
              }
            }
            

            comme quoi c'est le souk ; )

            • [^] # Re: sans prétentions

              Posté par . Évalué à 1.

              Je crois tu fais erreur, c'est juste la notation litérale avec ES6 :

                  let animal1 = {
                    // public member
                    animalType: 'animal',
              
                    // public method 
                    describe() {
                      return `This is "${this.animalType}"`;
                    }
                  };
              

              …pas une fonction fléchée.

              • [^] # Re: sans prétentions

                Posté par . Évalué à 0.

                Fonction littérale ? euh ouais peut être xd ou peut être pas xb

                La mdn n'en fait pas référence,
                - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions

                Une histoire de souk ; )

                Et si je reprends https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions/Fonctions_fl%C3%A9ch%C3%A9es

                Je ne vois pas de contre indication à penser que c'est une Ffléchée, la fonction est anonyme, la syntaxe est respectée, elle est attachée a animalType2.describe.

                Perso je me suis jamais posé ce genre de questions, en fait.

                Quand je fais une fonction,
                je me demande si je veux lui donner un nom, pour mieux debugger,
                et je regarde son scope, pour savoir si ça colle.

                • [^] # Re: sans prétentions

                  Posté par . Évalué à 0.

                  si je trouves je te posterais un lien vers des bonnes pratiques JS qui me semble sont plus importante,

                  D'ici là tu peux te tenter
                  - https://github.com/stevekwan/best-practices/blob/master/javascript/best-practices.md
                  - http://jstherightway.org/

                  Mais ce n'est pas exactement le genre concision que j'aurais voulu avoir, désolé.

                • [^] # Re: sans prétentions

                  Posté par . Évalué à 2. Dernière modification le 01/04/16 à 09:21.

                  Si si, c'est l'écriture qu'on trouve aussi dans le MDN, par exemple ici : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_Types#Object_literals.

                  Par contre, et tu touche un point important, je ne me suis pas posé la question de savoir si avec cette écriture, la fonction est anonyme ou pas.

                  Je vais tester ça, ça me fera progresser. Merci !

                  Concernant les "Best Practises", merci pour les liens, je verrai ça quand j'aurais fini le bouquin de Crockford.

                  • [^] # Re: sans prétentions

                    Posté par . Évalué à -1.

                    au risque de paraphraser tes lectures et ta compréhension,

                    Ta remarque initiale portait sur la notion de fonction littérale.

                    Hors le lien que tu pointes porte sur la notion d'objet littérale.

                    // notation non littérale
                    function CarNonLitterale (name) {
                      this.myCar = 'some';
                      this.getCar = function(){}
                      // etc
                    }
                    
                    // notation littérale
                    var carLitterale = { myCar: "Saturn", getCar: carTypes("Honda") };

                    En me basant sur la définition suivante de la mdn,

                    An object literal is a list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}).

                    Pour en revenir sur la notion de fonction littérale, j'avoue que malgré mes recherches je n'ai pas de certitude. Je suspectes que c'est une notion importée.

                    Autre chose, Ffléché est obligatoirement anonyme, cela fait partie de sa définition (mdn again),

                    Une expression de fonction fléchée (arrow function en anglais) permet d'avoir une syntaxe plus courte que les expressions de fonction et permet de lier la valeur this de façon lexicale (elle ne lie pas ses propres this, arguments, super, ou new.target). Les fonctions fléchés sont obligatoirement anonymes.

                    Des questions intéressantes !

                    • [^] # Re: sans prétentions

                      Posté par . Évalué à 1.

                      Merci. D'ailleurs, quelqu'un ma donné la syntaxe correcte pour ajouter les fonctions dans ma factory, mais c'est assez lourdingue. Je vais tenter autre chose, plus simple.

                      Cela dit, de nos discussions, j'ai sorti quelque chose qui met en exergue tes remarques, notamment sur les fonctions anonymes.

                      // ES6 only
                      'use strict';
                      
                      // The base object, "object literal" syntax
                      let animal = {
                        // public member
                        animalType: 'animal',
                      
                        // public method 
                        describe() {
                          // This way, we get sure "this" is correctly bound to the animal instance
                          return `This is "${this.animalType}"`;
                        },
                      
                        // By using a recursive functions,
                        // we're going to check whether such function syntaxes are anonymous or not.
                      
                        // anonymous
                        fact(n) {
                          if(n === 1) {
                            return 1;
                          }
                          return (n * this.fact(n - 1)); // "fact" is anonymous and can't be called from itself
                        },
                      
                        // not anonymous
                        fact2: function fact2(n) {
                          // By using a recursive function,
                          // we're going to check whether such function syntax is anonymous or not.
                          if(n === 1) {
                            return 1;
                          }
                          return (n * fact2(n - 1));
                        }
                      };
                      
                      // Have a look at the prototype chain
                      console.log(animal.__proto__);
                      console.log(animal.__proto__.__proto__);
                      
                      // Call of a public method
                      console.log(animal.describe()); // This is "animal"
                      console.log(animal.fact(3)); // 6
                      console.log(animal.fact2(3)); // 6
                      

                      Donc ok, la première forme est anonyme. Mais le "this" est correctement attaché. Je n'ai pas l'expérience suffisante de Js pour juger la réelle différence entre les 2. Mais au moins, maintenant, je sais. A l'écriture cependant, y a pas photo je trouve.

                      • [^] # Re: sans prétentions

                        Posté par . Évalué à -1.

                        J'ai l'impression que la déclaration de fact2 en tant que fonction est oubliée. Il ne connait plus que la méthode animal.fact2.
                        Aussi, si tu jettes des exceptions depuis fact / fact2, il renvoies des jolies stack traces (ex.stack) qui remonte les noms de méthode de l'objet.
                        Bref, je ne vois pas de différences notable à mon humble niveau.

                        je rajoutes un exemple au cas où cela t'en soulève une, de gauche ou de droite.

                        // ES6 only
                        'use strict';
                        
                        // The base object, "object literal" syntax
                        let animal = {
                          // public member
                          animalType: 'animal',
                        
                          // public method
                          describe() {
                            // This way, we get sure "this" is correctly bound to the animal instance
                            return `This is "${this.animalType}"`;
                          }
                        };
                        
                        let cahuete = {
                          animalType: 'cahuete'
                        };
                        
                        cahuete.describe = animal.describe;
                        
                        console.log(cahuete.describe()) // => This is "cahuete"
                        • [^] # Re: sans prétentions

                          Posté par . Évalué à 1.

                          Ah bonne idée ça de tester avec les exceptions. J'y avais pas pensé et c'est surement là que je vais pouvoir faire quelques tests intéressants. Encore merci du coup, tu m'auras permis de bien progresser dans ce long chemin qu'est Js.

                          Et dire qu'on lit partout que Js est "facile", quelle blague !

                          • [^] # Re: sans prétentions

                            Posté par . Évalué à -1.

                            et moi qui pensais justement "et encore ce n'est que this.." y'a pas 5 minutes : )

                            Juste au cas où, jettes un œil aussi à bind / call / apply et self pour en finir, je crois, avec this.

                            Restera plus que les streams, les promesses, la pseudo programmation fonctionnelle, la boucle d' évènements (nextTick, setTimeout(fn,0), setImmediate), éviter le callback hell (return first), faire un paquet npm, le semver, les frameworks de tests, les opérations de lint, la browserification, la chaine de build (grunt, gulp, npm sripts ect) et d'autres trucs que j'oublie probablement !

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.