I. Avant-propos

JsPackage est un framework JavaScript de programmation orientée objet. Il permet la création d'un ensemble d'objets, tels que des classes, des packages, des interfaces ou encore des namespaces, afin de faciliter la conception et la lisibilité d'application complexes. De plus, la gestion des éléments privés permet de créer efficacement un code solide et sûr.

Le framework n'utilise que des fonctionnalités de JavaScript 1.8.5 (Ecmascript 5), ce qui le rend compatible avec la majorité des plateformes, telles qu'Internet Explorer (9+), Google Chrome, Firefox, mais aussi Node.js.

Pour utiliser JsPackage, importer simplement le fichier JsPackage.jsJsPackage.js de GitHub dans votre fichier HTML ou votre script Node.js. Ce fichier va ajouter dans l'environnement global (window en HTML ou global dans Node.js) les différents objets nécessaires à l'utilisation de JsPackage.

La documentation est accessible en français et en anglais à cette adresse : wiki.jspackage.netwiki.jspackage.net.

II. Présentation

En plus des classes, JsPackage permet de créer des packages. Les packages sont des objets pouvant comporter des propriétés de différents types qui peuvent être publiques ou privées :

  • des variables / constantes ;
  • des fonctions ;
  • des classes ;
  • d'autres packages.

Les propriétés privées ne sont visibles que depuis les classes et fonctions du package. Ces propriétés seront aussi accessibles aux fonctions et classes des packages associés.

III. Créer un package

Les packages se créent via la fonction Package. La fonction accepte un objet comme paramètre. Chaque clé de l'objet peut correspondre à une variable, constante ou fonction. Voici un exemple simple de création de package :

 
Sélectionnez
var APackage = Package({
     aFunction: function() {
        return this.aVariable;
     }
   , aVariable: 'a value'
});

Dans cet exemple, on crée un package comportant une fonction et une variable. La fonction et la variable sont toutes deux publiques.

Pour déclarer une propriété privée, on utilisera le descripteur Private.

 
Sélectionnez
var OnePackage = Package({
     privateVariable : Private('a value')
   , getPrivateVariable : function() {
         return this.privateVariable;
     }
   });
   
   console.log(OnePackage.getPrivateVariable()); // 'a value'  
});

IV. Déclarer un objet comme parent

Pour associer une classe ou un autre package à notre package, il faut déclarer celui-ci comme étant parent de la classe/package à associer. Les classes/packages ainsi associés pourront accéder aux propriétés privées du package parent, et réciproquement.

L'association se fait en trois temps :

  • déclaration de la propriété dans le package ;
  • une classe ou un package déclare le package comme étant son parent ;
  • le package parent est scellé de façon à ce qu'aucun autre élément ne puisse être ajouté.

Voici un exemple :

 
Sélectionnez
var Geometry = Package({
     $ : { seal : false }
   , Shapes2D : Public
});

var Shapes2D = Package({
     $ : { name   : 'Shapes2D'
         , parent : Geometry
         , seal   : false}
   , Circle : Public
});

Class({
     $ : { name           : 'Circle'
         , parent         : Shapes2D
         , initialization : function(radius) {
              this.radius = radius;
           }}
   , radius : Private
   , getRadius : Public(function() {
         return this.radius;
      })
});

Geometry.$.seal();

var circle10 = Geometry.Shapes2D.Circle(10);

console.log(circle10.getRadius()); // 10

Plusieurs choses sont à noter :

  • la propriété $.parent permet de connaître le package parent du package ;
  • sceller le package père scellera le package fils aussi. En fait, sceller le package parent scellera l'ensemble des packages fils, petits-fils, etc. ;
  • sceller un package fils scellera automatiquement son package parent ;
  • Tant que le package parent n'est pas scellé, le package fils n'a pas accès à ces attributs privés. En fait, la propriété $.parent retournera undefined.

V. Accès aux éléments privés

V-A. this()

Le mot-clé standard this est probablement le moyen le plus simple et le plus pratique pour accéder aux éléments privés d'autres objets. En temps normal, this est un objet. Au sein des fonctions et méthodes crées par JsPackage, this est une fonction. Croyez-le ou non, cela reste standard (pour les plus sceptiques, jetez un coup d'œil sur la fonction Object.apply) !

 
Sélectionnez
var Geometry = Package({
     $ : { seal : false }
   , Circle : Public
   , changeRadius : function(circle1, newRadius) {
         this(circle1).radius = newRadius;
      }
});

Class({
     $ : { name           : 'Circle'
         , parent         : Geometry
         , initialization : function(radius) {
              this.radius = radius;
           }}
   , radius : Private
   , getRadius : Public(function() {
         return this.radius;
      })
});

Geometry.$.seal();

var circle10 = Geometry.Circle(10);
console.log(circle10.getRadius()); // 10

Geometry.changeRadius(circle10, 100);
console.log(circle10.getRadius()); // 100

En réalité, this peut être utilisé sur n'importe quel objet, y compris des packages ou des classes/instances d'un autre package, ou même des simples objets, des tableaux, etc. Toutes les fonctions de packages ou méthodes de classes/instances peuvent utiliser this de cette manière. this retournera l'objet avec tous les éléments auxquels la fonction/méthode a accès.

 
Sélectionnez
var Geometry = Package({
     $ : { seal : false }
   , Shapes2D : Public
   , getPrivate :
      function(obj) {
         return this(obj);
      }
   , packageName : Private('Geometry')
});

var Shapes2D = Package({
     $ : { name   : 'Shapes2D'
         , parent : Geometry
         , seal   : false}
   , Circle : Public
   , packageName : Private('Shapes2D')
});

Class({
     $ : { name           : 'Circle'
         , parent         : Shapes2D
         , initialization : function(radius) {
              this.radius = radius;
           }}
   , radius : Private
   , getRadius : Public(function() {
         return this.radius;
      })
   , className : Static.Private('Circle')
});

Geometry.$.seal();

var circle10 = Geometry.Shapes2D.Circle(10);

console.log(Geometry.getPrivate(Geometry).packageName);      // 'Geometry'
console.log(Geometry.getPrivate(Shapes2D).packageName);      // 'Shapes2D'
console.log(Geometry.getPrivate(Shapes2D.Circle).className); // 'Circle'
console.log(Geometry.getPrivate(circle10).radius);           // 10

S'il n'y a aucun droit particulier, alors on retournera simplement l'objet tel quel.

 
Sélectionnez
var TestThis = Package({
     doThis :
      function(value) {
         return this(value);
      }
});

console.log(TestThis.doThis(1))   // 1
console.log(TestThis.doThis('a')) // 'a'
console.log(TestThis.doThis([]))  // []
console.log(TestThis.doThis({}))  // {}

V-B. $.parent

Au sein d'une classe, d'une instance de classe ou d'un package, la propriété $.parent permet d'accéder à l'ensemble des éléments publics et privés du package. Exemple :

 
Sélectionnez
var society = Package({
     $ : { seal : false }
   , Employee     : Public
   , employeeList : []
   , getList      : function() {
        return this.employeeList;
     }
});

Class({
     employeName : Private
   , getName     : function() {
        return this.employeName;
     }
   , $ : { initialization : function(employeName) {
              this.employeName = employeName;
              this.$.parent.employeeList.push(this);
           }
         , name           :'Employee'
         , parent         : society
     }
});

society.$.seal();

var JohnSmith = new society.Employee('JohnSmith');

console.log(JohnSmith.getName()); // 'JohnSmith';

VI. Masquer un package

Tous les packages fils, petits-fils, etc. d'un même package ont accès aux propriétés privées des uns et des autres. Cela peut être handicapant quand certains packages ou certaines classes doivent être publics : on ne souhaite pas forcément que tous les packages soient publics.
Il est possible pour un fils de masquer l'existence de son parent. Pour cela, on déclarera le parent comme privé. La propriété $.parent du package ou de la classe retournera alors undefined depuis l'extérieur du package.

 
Sélectionnez
var society = Package({
     $ : { seal : false }
   , Employee     : Public
   , employeeList : []
   , getList      : function() {
        return this.employeeList;
     }
});

Class({
     employeName : Private
   , getName     : function() {
        return this.name;
     }
   , $ : { initialization : function(employeName) {
              this.employeName = employeName;
              this.$.parent.employeList.push(this);
           }
         , name           :'Employee'
         , parent         : Private(society)
     }
});

society.$.seal();

console.log(society.Employee.$.parent); // undefined

VII. Conclusion

Les packages permettent de faciliter la conception et l'organisation du code. Ils ont été pensés pour isoler facilement les composants les plus sensibles avec un minimum d'effort.

VIII. Remerciements

Je tiens tout particulièrement à remercier Cyrille Gachot pour sa relecture et ses conseils sur ce tutoriel, Sophia Tewa et Torgar pour leur relecture orthographique.