наш блог

Наглядный пример использования паттерна «Модуль» в javascript

В отличие от большинства традиционных языков программирования, javascript не имеет модификаторов доступа, что в ряде ситуаций превращается в ощутимую проблему. Скажем, если есть желание скрыть переменные от сторонних разработчиков, а также реализовать некоторые методы класса или функции. Неудивительно, что вышеупомянутый барьер привел к появлению шаблона проектирования «Модуль», который относится к группе порождающих паттернов.

Реализовать вышеупомянутый паттерн в javascript позволяют немедленно вызываемые функции, от английского, IIFE – Immediately Invoked Function Expression. Возвращение из них объекта, свойства которого могут быть доступны тому или иному пользователю, создает замыкание.

Теперь рассмотрим детальнее:

const Module = (function() {
 let counter = 0;

 function increment() {
   return ++counter
 }
 function decrement() {
   return --counter
 }

 function getCounterVal()  {
   return counter
 }

return {
 getCounterVal,
 decrement,
 increment,
}

})();
console.log(Module.counter) // Выведет undefined

В примере видно, что доступ к переменной области counter закрыт именно извне, а при обращении к Module доступны только три метода: getCounterVal(), decrement() и increment().

Как известно, по умолчанию абсолютно все объекты в javascript являются мутабельными – такими, которые можно преобразовать. То есть, чтобы ограничить возможность внутреннего переопределения или добавления методов и свойств, необходимо изменить возвращаемый объект, заморозив его свойства методом Object.freeze().

return Object.freeze({
 getCounterVal,
 decrement,
 increment
})

Вышеупомянутые манипуляции позволяют блокировать любые попытки преобразования метода возвращаемого объекта. Все они будут пресекаться либо выводом ошибки, либо игнорированием строки.

Впрочем, существует еще один важнейший нюанс. Дело в том, что Object.freeze() не работает на вложенные объекты или массивы. А значит, их по-прежнему можно изменить. Скрипт запускается командой node имя_файла.js:

const Module = (function() {
 let counter = 0;

 function increment() {
   return ++counter
 }
 function decrement() {
   return --counter
 }

 function getCounterVal()  {
   return counter
 }

return Object.freeze({
 getCounterVal,
 decrement,
 increment,
obj: {
 a: 20
}

})();

Module.obj.a = 30;
console.log(Module.obj.a) // Выведет 30

Решить проблему позволит функция, способная заморозить все вложенные объекты, которые возвращаются модулем.

function freezeAllObject(obj) {
 Object.keys(obj).map(key => {
     if (typeof obj[key] === 'object' && obj[key] !== null)
       freezeAllObject(obj[key])
   });

 return Object.freeze(obj)
}

В функции нужно пройтись по всем полям объекта. Если тип содержимого в одном из его полей окажется объектом (необходима проверка на null, так как typeof null возвращает ‘object’), то следует рекурсивно вызывать функцию freezeAllObject. В итоге она вернет замороженный объект.

Итак, если теперь предпринять попытку изменить любой вложенный объект или массив (из числа возвращаемых модулем), то произойдет один из двух вариантов – либо появится исключение TypeError, либо строка попросту будет проигнорирована.

Полный код:

function freezeAllObject(obj) {
 Object.keys(obj)
   .map(key => {
     if (typeof obj[key] === 'object' && obj[key] !== null)
       freezeAllObject(obj[key])
   });

 return Object.freeze(obj)
}

const Module = (function() {
 let counter = 0;

 function increment() {
   return ++counter
 }
 function decrement() {
   return --counter
 }

 function getCounterVal()  {
   return counter
 }

 return freezeAllObject({
   getCounterVal,
   decrement,
   increment,
   obj: {
     a: 20
   }
 })
})();

console.log(Module.getCounterVal()); // Выведет 0
Module.increment();
Module.increment();
console.log(Module.getCounterVal()); // Выведет 2
Module.decrement();
console.log(Module.getCounterVal()); // Выведет 1
console.log(Module.obj.a); // Выведет 20
Module.obj.a = 30;
console.log(Module.obj.a); // Выведет 20

Как видно, использование паттерна «Модуль» позволяет создавать в объекте как приватные, так и публичные переменные и методы. А применение функции Object.freeze() делает объект иммутабельным.

Юрий Кизилов, компания Craft Group