суббота, Июль 22, 2006

js singleton

In software engineering, the singleton design pattern is used to restrict instantiation of a class to one (or a few) objects.
Да, в js нет классов, но инкапсуляция поддерживается, потому примитивная реализация паттерна синглтона иногда может быть полезна. Например, если конструктор объекта выполняет кучу долгих (или необратимых) операций (например, с DOM'ом) и нет возможности проследить за инстанциацией вручную (что в такой неоднородной среде, как веб-приложение иногда бывает крайне сложно), удобно описать конструктор так, чтобы он выполнился лишь единожды.
В языках с классическим ООД синглтон реализуется просто:
  • запрещается доступ к конструктору
  • добавляется статическое свойство для хранения ссылки на объект
  • добавляется статический метод, проверяющий соответствюущее свойство на наличие в нём ссылки на объект, в случае отсутствия инстанцирует объект, кладёт ссылку на него в свойство и возвращает
  • по вкусу добавляются приправы вроде запрещения клонирования и и прочая
Но в js невозможно (и бессмысленно) запрещать доступ к конструктору, т.к. функция-конструктор — и есть смысловая единица ООП в js, она является единственным возможным вариантом построения объекта, да и модификаторы доступа отсутствуют. Потому единственная возможность реализации паттерна — заключение конструктора создаваемого объекта в конструктор-синглтон. Таким образом, сообразно принципу замыканий, мы закрываем доступ к инстанциации объекта. Иллюстрируем: var singleton = function(){
  var obj = function() { ...dosmth...};
  var instance = new obj;
}
Можно сократить до: var singleton = function(){
  var instance = new function() { ...dosmth...};
}
Все бы хорошо, но пока что синглтоном тут и не пахнет — каждый раз при инстанциации объекта (var stmh = new singleton();) в его локальной области видимости анонимным конструктором(т.к. мы не указали имя, а просто написали new function(){}) будет создаваться объект, и соответственно всё, что прописано в его конструкторе, будет выполнено. Итак, следующим шагом будет «синглтонизация» нашего объекта. Наиболее простым вариантом будет вынести переменную, хранящую ссылку на инстанцированный объект вовне нашего конструктора синглтона. var instance = false;
var singleton = function(){
  if (!instance) instance = new function() { ...dosmth...};
  return instance;
}
Для наглядности вместо dosmth подставим var rnd = Math.random(); this.echo = function(){ alert(rnd); }; Т.е. добавим private-свойство и public-метод, если выражаться в терминах классического ООП. Итого: var instance = false;
var singleton = function(){
  if (!instance) instance = new function() {
    var rnd = Math.random();
    this.echo = function(){ alert(rnd); };
  };
  return instance;
}
Всё вроде замечательно работает, но только инкапсуляция нарушена — используется глобальная переменная там, где ее быть не должно. Статическое свойство удобнее всего описать с помощью свойства arguments.callee. Для примера считаем числа фибоначчи: По-обычному: function fib(x){
  if (x == 0) return 0; if (x==1) return 1;
  return fib(x-1)+fib(x-2);
}
for (var x=0; x<20; ++x) document.write(fib(x) + "<br>");
и посредством анонимной функции: var fib = function(x) {
  if (x == 0) return 0; if (x == 1) return 1;
  return arguments.callee(x-1) + arguments.callee(x-2);
};
for (var x=0; x<20; ++x) document.write(fib(x) + "<br>");
Как видно, arguments.callee было задумано как свойство, хранящее ссылку на текущую функцию. Т.к. в js почти всё — объект, и к тому же присвоение несуществующему свойству значения создает это свойство, мы будем хранить ссылку на созданный объект в свежесозданном свойстве объекта arguments.callee. var singleton = function(){
 if (typeof arguments.callee.instance=='undefined'){
  arguments.callee.instance = new function(){
    var rnd = Math.random();
    this.echo = function(){ alert(rnd); };
  };
 }
 return arguments.callee.instance;
};
Усё, глобальная область видимости не пачкается, и синглтон во всей его красе. Сколько бы мы не инстанцировали объектов, rnd будет выведен один и тот же.

Powered by Blogger