JS

Design Patterns

Agenda

  1. Module
  2. Singleton
  3. Observer
  4. PubSub
  5. Mediator
  6. Facade

Design patterns (GOF)

Шаблоны проектирования - это повторяющиеся решения общих проблем в разработке программного обеспечения.

Design patterns (GOF)

  • Порождающие паттерны - предназначены для создания новых объектов в системе.

  • Структурные паттерны - решают задачи компоновки системы на основе классов и объектов (рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры).

  • Паттерны поведения - предназначены для распределения обязанностей между объектами в системе.

Design Patterns Elements of Reusable Object-Oriented Software

Module

«Модуль» — это популярная реализация паттерна, инкапсулирующего приватную информацию, состояние и структуру, используя замыкания. Это позволяет оборачивать публичные и приватные методы и переменные в модули, и предотвращать их попадание в глобальный контекст, где они могут конфликтовать с интерфейсами других разработчиков. Паттерн «модуль» возвращает только публичную часть API, оставляя всё остальное доступным только внутри замыканий.

Module

const counter = {
    counter: 0,
    incrementCounter: function () {
        return ++this.counter;
    },
    resetCounter: function () {
        return this.counter = 0;
    }
};

Module

const counterModule = (function () {
    // private code

    return {
        // public interface or methods
    }
})();

Module

var counterModule = (function () {
    var counter = 0;

    return {
        incrementCounter: function () {
            return ++counter;
        },
        resetCounter: function () {
            return counter = 0;
        }
    }
})();

Module

var wrapperId = 'counter';

var counterModule2 = (function (id) {
    var counter = 0;

    console.log(id);

    return {
        incrementCounter: function () {
            return ++this.counter;
        },
        resetCounter: function () {
            return this.counter = 0;
        }
    }
})('123');

with import

Module

var counterModule3 = (function () {
    var counter = 0;
    var module = {};

    module.incrementCounter = function () {
        return ++this.counter;
    };

    module.resetCounter = function () {
        return this.counter = 0;
    };

    return module;
})();

with export

Module

Это хорошее решение для того, чтобы скрыть внутреннюю логику от посторонних глаз и производить всю тяжелую работу исключительно через интерфейс, который вы определите для использования в других частях вашего приложения. Этот паттерн очень похож на немедленно-вызываемые функции (IIFE), за тем исключением, что модуль вместо функции, возвращает объект.

Bonus Module

var Neuron = (function (jQuery, Underscore) {
    var Cell = function () {};

    function say(message) {
        console.log(message);
    }

    Cell.prototype = {
        migrate: function() {
            // cell is going to find a better place
            say('travelling');
        },
        learn: function(subj) {
            // trying something new
            say('f***ing ' + subj);
        },
        duplicate: function() {}
    };
    return Cell;
})($, _);

// Usage:
// Creates new neuron instance
var brainCell = new Neuron(); // Outputs: travelling
brainCell.migrate(); // Outputs: f***ing math
brainCell.learn('math');

Module Constructor

Singleton

Wherefore:
Provide single instance of class. When you try to create another instance, the program should receive an item that has already been created.


How to:
Using module pattern

Singleton

var Singleton = (function () {
    var instance;

    function createInstance() {
        var object = new Object("I am the instance");
        
        return object;
    }

    return {
        getInstance: function () {
            // creates instans if it doesn't exist
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();
// Usage:
// Creates new instance
var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();
// Outputs: true
console.log(instance1 === instance2);

Observer/PubSub

Wherefore:
This is where the objects in a system may subscribe to other objects and be notified by them when an event of interest occurs.


How to:
Using javascript events

Observer

Observer представляет собой не что иное, как связь один-ко-многим. В упрощенном виде этот паттерн состоит из объекта наблюдения (subject) и наблюдателей (observers).

Принципиальная схема взаимодействия выглядит так:

  • Subject — реализует методы: observe, detach, notify, get, set.
  • Observer — реализует метод update.
  •  

Также subject содержит ссылки на всех observers, которые его слушают, а observer, в свою очередь содержит ссылку, на subject, на который он подписан.

Observer

Observer

$('button').on('click', function(){
    alert('button clicked');
});
$('button').on('click', function(){
    alert('button clicked');
});

Pub-Sub

Pub-sub паттерн является одной из вариаций паттерна Observer. Исходя из названия в паттерне выделяют два компонента Publisher (издатель) и Subscriber (подписчик). В отличие от Observer, связь между объектами осуществляется посредством канала связи Event Channel (шины событий).

Publisher кидает свои события в Event Channel, а Subscriber подписывается на нужное событие и слушает его на шине, что обеспечивает отсутствие прямой связи между подписчиком и издателем.

Observer and Pub-Sub

Observer and Pub-Sub

Таким образом можно выделить основные отличительные особенности между Pub-sub и Observer:

  1. отсутствие прямой связи между объектами
  2. объекты сигнализируют друг другу событиями, а не состояниями объекта
  3. возможность подписываться на различные события на одном объекте с различными обработчиками

Observer and Pub-Sub

(function( $ ) {
    var o = $( {} );

    $.each({
        trigger: 'publish',
        on: 'subscribe',
        off: 'unsubscribe'
    }, function( key, val ) {
        jQuery[val] = function() {
            o[key].apply( o, arguments );
        };
    });
})( jQuery );

// $.getJSON('http://search.twitter.com/search.json?q=dogs&callback=?', function( results) {
//     $.publish( 'twitter/results', results );
// });

setTimeout(function() {
    $.publish( 'twitter/results', { results: [{text: 'test'}] } );
}, 2000);

$.subscribe( 'twitter/results', function( e, results ) {
    $('body').html(
        $.map( results.results, function( obj, index) {
            return '<li>' + obj.text + '</li>';
        }).join('')
    );
});

Pub-Sub

class PubSub {
    constructor() {
        this.handlers = [];
    }
    subscribe(event, handler, context) {
        if (typeof context === 'undefined') { context = handler; }
        this.handlers.push({ event: event, handler: handler.bind(context) });
    }
    publish(event, args) {
        this.handlers.forEach((topic) => {
            if (topic.event === event) {
                topic.handler(args)
            }
        })
    }
}
class ChatRoom {
    constructor() {
        this.pubsub = new PubSub();
        this.pubsub.subscribe('event-message', this.emitMessage, this);
    }
    emitMessage(msg) {
        console.group('PubSub');
        console.log('user sent message!', msg);
        console.groupEnd();
    }
    sendMessage() {
        this.pubsub.publish('event-message', 'Hey, how are you?');
    }
}
var room = new ChatRoom();
room.sendMessage();

Mediator

Wherefore:
Mediator is a behavioral design pattern that allows us to expose a unified interface through which the different parts of a system may communicate.


How to:
Using additional object

Mediator

На основе pub-sub строится работа паттерна Mediator, который позволяет наладить коммуникацию между различными компонентами системы. Mediator представляет собой глобальный объект в системе, о котором знают все компоненты системы, при этом компонент может выступать как слушателем события, так и издателем другого события, таким образом налаживая коммуникацию между объектами системы.

Mediator

/*
 * Mediator Pattern
 */
class Mediator extends PubSub {
    constructor(opts) {
        super(); // get handlers
    }

    attachToObject(obj) {
        obj.handlers = [];
        obj.publish = this.publish;
        obj.subscribe = this.subscribe;
    }
}

var mediator = new Mediator();

var myRoom = {
    name: 'myRoom'
};

mediator.attachToObject(myRoom);

myRoom.subscribe('connection', function() {
    console.group('Mediator');
    console.log(`user connected to ${myRoom.name}!`);
    console.groupEnd();
}, myRoom);

myRoom.publish('connection');

Facade

Wherefore:
This pattern provides a convenient higher-level interface to a larger body of code, hiding its true underlying complexity.


How to:
Creating set of facade methods and joining them in one place

Facade

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

Facade

var module = (function() {
  var _private = {
    i: 5,
    get: function() {
      console.log('Текущее значение:' + this.i);
    },
    set: function(val) {
      this.i = val;
    },
    run: function() {
      console.log('процесс запущен');
    },
    jump: function() {
      console.log('резкое изменение');
    }
  };
  return {
    facade: function(args) {
      _private.set(args.val);
      _private.get();
      if (args.run) {
        _private.run();
      }
    }
  }
}());

module.facade({run:true, val:10});

Facade

function Facade() {
    function getRequest(url, data, callback) {
        setTimeout(function () {
            return callback([{ name: 'Arni' }]);
        }, 1000);
    }

    this.getResult = function(url, data, callback) {
        getRequest(url, data, callback);
    };

    return this;
}

Facade

var BannerModule = function(options) {
    var params = options || {},
        doc = document,
        facade = new BannerApiFacade(),
        ui = {
            'close': doc.getElementById(params.close),
        },
        helper = {
            applyData: function(options) {
            }
        },
        events = {
            ...
        };
    //events mapping
    for (var key in ui) {
        ui[key].onclick = (function(key){
            return function(event) {
                events[key]();
            };
        })(key);
    }
    function successCallback(data) {
    };
    return {
      loadData: function() {
        facade
          .getResult("GET", 'localhost:4000', successCallback);
      }
    };
};
var banner = new BannerModule({
    close: 'close',
    image: 'image',
    download: 'download'
});

banner.loadData();

Links

Examples

JS Design Patterns

By Oleg Rovenskyi

JS Design Patterns

Design Patterns

  • 887