Шаблоны программирования Javascript (часть II)

Открытый модуль

Основываясь на опыте использования паттерна «модуль», Christian сделал то что он назвал «открытый модуль» (Revealing Module). Как следует из названия он похож на паттерн «модуль», но чуть более структурирован и понятен, особенно когда речь идет о совместной разработке.
Во-первых, он так же основан на функции, которая определяется и сразу выполняется:

  1. var anchorChange4 = function () {}();

После этого определим все свойства и методы, не разделяя их на публичные и защищенные:

  1. // это будет private свойство
  2. var config = {
  3. colors: [ “#F63”, “#CC0”, “#CFF” ]
  4. }
  5. // это будет public метод
  6. var init = function () {
  7. var self = this; // сохраняем ссылку на this в self
  8. // получаем все ссылки на странице
  9. var anchors = document.getElementsByTagName(“a”);
  10. var size = anchors.length;
  11. for (var i =0; i < size; i++) {
  12. anchors[i].color = config.colors[i];
  13. anchors[i].onclick = function () {
  14. self.changeColor(this, this.color);
  15. return false;
  16. };
  17. }
  18. }
  19. // это будет public метод
  20. var changeColor = function (linkObj, newColor) {
  21. linkObj.style.backgroundColor = newColor;
  22. }

Теперь самое интересное. Выше, в описании паттерна «модуль», я уверен, вы заметили, что он возвращает объект со всеми публичными свойствами и методами. В Открытом модуле вы возвращаете только ссылки на те свойства и методы, которые хотите показать.

  1. return {
  2. // перечисляем все public методы и свойства
  3. init: init,
  4. changeColor: changeColor
  5. }

Только два метода должны быть публичными init (который делает все подготовительную работу) и changeColor (который вызываем, когда щелкаем по ссылке). Глядя на код сразу видно какие свойства и методы общедоступны. Окончательный код выглядит следующим образом:

  1. var anchorChange4 = function () {
  2. // это будет private свойство
  3. var config = {
  4. colors: [ “#F63”, “#CC0”, “#CFF” ]
  5. }
  6. // это будет public метод
  7. var init = function () {
  8. var self = this; // сохраняем ссылку на this в self
  9. // получаем все ссылки на странице
  10. var anchors = document.getElementsByTagName(“a”);
  11. var size = anchors.length;
  12. for (var i =0; i < size; i++) {
  13. anchors[i].color = config.colors[i];
  14. anchors[i].onclick = function () {
  15. self.changeColor(this, this.color);
  16. return false;
  17. };
  18. }
  19. }
  20. // это будет public метод
  21. var changeColor = function (linkObj, newColor) {
  22. linkObj.style.backgroundColor = newColor;
  23. }
  24. return {
  25. // перечисляем все public методы и свойства
  26. init: init,
  27. changeColor: changeColor
  28. }
  29. }();

Как и в других паттернах, надо вызвать соответсвующую функцию в блоке script:

  1. <script type=”text/javascript”>
  2. anchorChange4.init();
  3. </script>

И снова пример для этого паттерна.

Объект

Создание нового объекта класса (его экземпляра) существует во всех объектно-ориентированных языках. Но Javascript скорее объекто-прототипированный, а не объектно-ориентированный язык. В Javascript вам надо вызвать конструктор вашего объекта с соотвествующими параметрами для создания объекта. В нем нет классов и подклассов, как в Java.
Во-первых, необходимо написать конструктор вашего объекта:

  1. var anchorChanger = function () {};

Мы оставили контруктор пустым (и это видно), но позже добавим вызов метода init этого объекта.
Используя прототип, мы можем добавить дополнительные свойства в объект, для того чтобы они сразу были доступны в нем. Я добавил 3 свойства: config, changeColor и init:

  1. anchorChanger.prototype.config = {
  2. colors: [ “#F63”, “#CC0”, “#CFF” ]
  3. }
  4. anchorChanger.prototype.changeColor = function (linkObj, newColor) {
  5. linkObj.style.backgroundColor = newColor;
  6. };
  7. anchorChanger.prototype.init = function () {
  8. var self = this;
  9. var anchors = document.getElementsByTagName(“a”);
  10. var size = anchors.length;
  11. for (var i =0; i < size; i++) {
  12. anchors[i].color = self.config.colors[i];
  13. anchors[i].onclick = function () {
  14. self.changeColor(this, this.color);
  15. return false;
  16. };
  17. }
  18. };

Как отмечает Mike West, использование прототипа более эффективно, чем добавление этих свойств «на лету» при каждом создании объекта. Окончательный объект выглядит следующим образом:

  1. // конструктор объекта
  2. var anchorChanger = function () {
  3. this.init();
  4. };
  5. anchorChanger.prototype.config = {
  6. colors: [ “#F63”, “#CC0”, “#CFF” ]
  7. }
  8. anchorChanger.prototype.changeColor = function (linkObj, newColor) {
  9. linkObj.style.backgroundColor = newColor;
  10. };
  11. anchorChanger.prototype.init = function () {
  12. var self = this;
  13. var anchors = document.getElementsByTagName(“a”);
  14. var size = anchors.length;
  15. for (var i =0; i < size; i++) {
  16. anchors[i].color = self.config.colors[i];
  17. anchors[i].onclick = function () {
  18. self.changeColor(this, this.color);
  19. return false;
  20. };
  21. }
  22. };

В документе надо создать новый экземпляр anchorChange:

  1. <script type=”text/javascript”>
  2. new anchorChanger();
  3. </script>

Пример тут.

Определение ленивых функций

Peter Michaux предложил паттерн, который он назвал ленивые функции. Этот паттерн обычно используется когда вам надо сделать вычисления только один раз, а потом использовать результаты многократно. Используя этот паттерн, вы можете быть уверены, что то что нужно сделать однократно, будет сделано однократно. В качестве примера Peter приводит объединеную для разных браузеров функцию прокрутки. В зависимости от браузера, функция прокрутки создается при первом ее выполнении.
В моем случае этот паттерн не дает каких-то реальных преимуществ, так как нет каких-либо объемных или сложных вычислений. Но как это работает в целом? В начале надо объявить вашу основную функцию:

  1. var anchorChange5 = function () {};

Теперь добавляем свойства, как и в предыдущих паттернах:

  1. var config = {
  2. colors: [ “#F63”, “#CC0”, “#CFF” ]
  3. };
  4. var anchors = document.getElementsByTagName(“a”);
  5. var size = anchors.length;
  6. for (var i =0; i < size; i++) {
  7. anchors[i].color = config.colors[i];
  8. anchors[i].onclick = function () {
  9. anchorChange5().changeColor(this, this.color);
  10. return false;
  11. };
  12. }

Все это, должно быть сделано только один раз и нет необходимости добавлять эти свойства в последующих вызовах этой функции. Чтобы использовать ленивое определение функции, ее необходимо переопределить:

  1. // переопределение функции для хранения исключительно changeColor
  2. anchorChange5 = function () {
  3. return {
  4. changeColor: function (linkObj, newColor) {
  5. linkObj.style.backgroundColor = newColor;
  6. }
  7. };
  8. };

Окончательный код с ленивым определением функции выглядит следующим образом:

  1. var anchorChange5 = function () {
  2. var config = {
  3. colors: [ “#F63”, “#CC0”, “#CFF” ]
  4. };
  5. var anchors = document.getElementsByTagName(“a”);
  6. var size = anchors.length;
  7. for (var i =0; i < size; i++) {
  8. anchors[i].color = config.colors[i];
  9. anchors[i].onclick = function () {
  10. anchorChange5().changeColor(this, this.color);
  11. return false;
  12. };
  13. }
  14. anchorChange5 = function () {
  15. return {
  16. changeColor: function (linkObj, newColor) {
  17. linkObj.style.backgroundColor = newColor;
  18. }
  19. };
  20. };
  21. };

Что при этом происходит:

1. anchorChange5 при вызове устанавливает переменную config и собирает все ссылки на странице;
2. Назначается событие onclick для ссылок, на которое будет вызываться метод changeColor в переопределенной функции anchorChange5
3. В конце концов anchorChange5 переопределяется и теперь хранит только метод changeColor, который общедоступен.

И опять вызываем функцию в документе:

  1. <script type=”text/javascript”>
  2. anchorChange5();
  3. </script>

Опять рабочий пример. Как уже было сказано раньше, этот паттерн не очень удобен, но рассмотрим ситуацию, когда есть тяжелые и сложные вычисления. Вы, конечно, не хотите делать их при каждом вызове функции. Peter называет такие определения функций «обещаными», что означает, что наша описание можно рассматривать как своего рода обещание, что в дальнейшем эта функция будет переопределенна и станет более эффективна.
Заключение

Как уже было сказано выше это только мой взгляд на решения этой задачи. Я уверен, что есть и другие решения, но цель этой статьи была изучить, как эти методы работаю в целом. Не существует одного мнения как лучше решить эту задачу. На мой взгляд, здесь singleton является наиболее удобным решением. Он довольно компактен. Но для других, более сложных проблем, использование модулей или создание объектов или определение ленивых функций может быть более подходящим паттерном

source

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: