Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dojo Singleton or at least static method/variable?

does anyone know how to make a Dojo Class a singleton, or at least how to create a static method or variable in a dojo class?

I currently achieve this by having a global Variable for each class and a method that sets this variable if its null, but this is a crappy solution. Having a singleton class would be much nicer because one could inherit from it and voilá has a singleton :)

heinrich

like image 302
Erik Avatar asked Dec 02 '10 11:12

Erik


People also ask

Which one should I choose static or singleton pattern?

A Singleton can implement interfaces, inherit from other classes and allow inheritance. While a static class cannot inherit their instance members. So Singleton is more flexible than static classes and can maintain state.

Can a singleton class have static variables?

Definitely the most common usage is that singleton is normal object that holds member variables. You can, for example, easily replace one object with another (with all other properties). Every class can have static variables but it does not deppend wheter it is singleton or not.

Should all singleton methods be static?

Singletons may or may not have state and they refer to objects. If they are not keeping state and only used for global access, then static is better as these methods will be faster. But if you want to utilize objects and OOP concepts (Inheritance polymorphism), then singleton is better.

Which is better singleton or static class?

A Singleton class can Dispose, while a static class can not. A Singleton class can have a constructor, while a static class can only have a private static parameterless constructor and cannot have instance constructors. A Static class has better performance since static methods are bonded on compile time.


2 Answers

The pattern for a singleton object in JavaScript is defined pretty well by the following article as background information:
http://kaijaeger.com/articles/the-singleton-design-pattern-in-javascript.html

To Dojo-tize this, using 1.7+ we need to capture the original constructor function in a closure so that nobody outside of the closure has access to the original and provide an accessor method that always returns the same instance, no matter who tries to get a reference to it...

It also makes sense to have the method that turns a "class" constructor Function into a singleon as a shareable, reuseable, piece of code. My preference in Dojo, is to have this as its own "module" as a utility method (not a Dojo class object), so here we go...

MakeSingleton.js

define(['dojo/_base/lang'], function(lang)
{
  return function(ctor) {   // not defining a class, just a utility method.
      var singletonCtor,    // our singleton constructor function.
          instance = null;  // singleton instance provided to any clients.

      // define the singleton constructor with accessor method.
      // (captures 'ctor' parameter and 'instance' variable in a function
      //  closure so that they are available whenever the getInstance() method
      //  on the singleton is called.)
      singletonCtor = new function() {       // note: 'new' is important here!!
          this.getInstance = function() {    // our accessor function
              if (!instance) {               // captures instance in a closure
                instance = new ctor();       // create instance using original ctor.
                instance.constructor = null; // remove instance's constructor method
              }                              //  so you cannot use new operator on it!!
              return instance;               // this is our singleton instance.
          }  
      };  

      // Since we are working with Dojo, when declaring a class object, if you
      // provide a 'className' as the first parameter to declare(...), Dojo will
      // save that value in the 'declaredClass' property on the object's prototype...
      // ... and adds a reference to that constructor function in the global namespace
      // as defined by the 'className' string.
      //
      // So if 'declaredClass' has a value, we need to close the hole by making
      // sure this also refers to the singleton and not the original constructor !!
      //
      if (ctor.prototype && ctor.prototype.declaredClass) {
        lang.setObject(ctor.prototype.declaredClass, singletonCtor);
      }

      // return the singleton "constructor" supports only a getInstance() 
      // method and blocks the use of the 'new' operator.
      return singletonCtor;

  }; // return "MakeSingleton" method
};  // define(...)

So how do we use this in Dojo 1.7+ when we want to define a Singleton class object? Pretty easy since we have already done the heavy lifting above...

MySingletonClass.js

define(['dojo/_base_declare', 'MakeSingleton'], 
       function(declare, MakeSingleton)
{
    return MakeSingleton( declare('MySingletonClass', [...], {
                          // Define your class here as needed...
                        }));  
});

So what is going on here... The result of calling declare(...) is passed directly into the MakeSingleton(...) utility method, so the original class constructor (Function) created by Dojo is never made public, and if a 'className' was passed into declare(...), MakeSingleton has also made sure that is not the original constructor, but the singleton object. Furthermore, the export from this module is also the singleton object (MakeSingleton's return value), so the Dojo loader only has a reference to the singleton after running the factory method. The original constructor class is captured within a closure of the singleton object, so nobody else can get to it and create an additional instance...
we truly have a singleton.

So how do we get access to this singleton... If you do not specify a 'className' when you declare your class, the only way to get at it is through a module dependency reference. If you did specify a 'className' as in the example above (shame, shame), you can access it from the global name space (which is NOT the way Dojo is headed, use the module dependency reference).

The result of calling the exported method of the MakeSingleton.js utility module is an object that has a single method on it called getInstance(). getInstance() will create an instance of the original class object on its first call, and return that same instance on every successive call. If you try to use 'new' on the singleton class, it will generate an error. If you try to use 'new' on the reference in the global namespace (if you provided a 'className' to declare), it will generate an error. The only way to get a hold of the instance is to call the singleton's getInstance() method.

SomeOtherModule.js

define(['dojo/_base/declare', 'MySingletonClass'],
       function(declare, MySingletonClass) 
{
    return declare(null, { 
        mySingleton: null,    // here we will hold our singleton reference.
        constructor: function(args) {
            ...
            // capture the singleton...
            mySingleton = MySingletonClass.getInstance();
            ... 
            mySingleton.doSomething(...);
        };

        mySpecialSauce: function(...) {
            mySingleton.doSomethingElse(...);
        };

        moreSauce: function(...) {
            var x;
            x = MySingletonClass.getInstance(); // gets same instance.
            x = new window.MySingletonClass();  // generates an error!!
            x = new MySingletonClass();         // generates an error!!
            // Dojo's loader reference generates an error as well !!
            x = new require.modules['MySingletonClass'].result(); 
        };
    });
});

It does not matter how many modules, classes, script elements, etc. get a reference to the singleton object, they will all be referring to the same instance and 'new' ones cannot be created.

like image 149
JonS Avatar answered Oct 31 '22 17:10

JonS


require(["dojo/_base/declare"], function (declare) {
var o =
    declare("com.bonashen.Singleton", null, {
        say : function (name) {
            console.log("hello," + name);
        }
    });
//define static getInstance function for com.bonashen.Signleton class.
console.debug("define getInstance function. ");
o.getInstance = function () {
    if (null == o._instance)
        o._instance = new o();
    return o._instance;
};});
like image 41
tony Avatar answered Oct 31 '22 16:10

tony