Удобно использовать Обозревателя на сайтах и в веб-приложениях, поэтому логично реализовать его с использованием самого популярного языка для веб-среды — Javascript.
Observable = function() { this.observers = []; } Observable.prototype.deliver = function(data) { for (var i in this.observers) { this.observers[i](data); } } Function.prototype.subscribe = function(observable) { observable.observers.push(this); return this; }
Всё просто. Класс Observable и есть субъект. Массив observers — подписчики, наблюдатели. Метод deliver оповещает наблюдателей об изменении состояния. Subscribe же подписывает наблюдателя. На практике всё выглядит так:
myClass = function() { this.value = 0; //некое значение this.onChange = new Observable(); //наблюдаемое состояние } myClass.prototype.change = function(new_value) { this.value = new_value; this.onChange.deliver(this.value); //изменилось значение — сообщили наблюдателям } var c = new myClass(); var write_log = function(value) { console.log(value); } write_log.subscribe(c.onChange);
Замечательно работающий пример: при каждом изменении c.value наблюдаемый onChange оповестит всех наблюдателей об изменении и сообщит им новое состояние, с которым наблюдатели распорядятся по-своему. Так, например, write_log() напечатает новое состояние в консоли. Но пример этот замечателен только до тех пор, пока не возникнет необходимости оперировать this'ом в исполняемой после рассылки функции.
Так, например, следующая конструкция работать как надо не будет:
myClass = function() { this.value = 0; this.onChange = new Observable(); } myClass.prototype.change = function(new_value) { this.value = new_value; this.onChange.deliver(this.value); } Logger = function(logtype) { this.type = (!!logtype) ? logtype : "alert"; } Logger.prototype.write = function(value) { if (this.type == "console") { console.log(value); return; } alert(value); } var c = new myClass(); var logger = new Logger("console"); logger.write.subscribe(c.onChange);
Проблема возникнет в обращении к this при исполнении logger.write. Напомню, что this — это контекст, а в данном случае контекстом будет не экземпляр класса Logger, а безымянная функция function(), вызванная при исполнении deliver.
Решить проблему помогает замечательная функция call, которая не только исполняет некий метод, но и позволяет указать контекст исполнения. Поэтому я немного переписал метод наблюдаемого — deliver и, соответственно, изменил механизм подписок.
Observable = function() { //без изменений this.observers = []; } Observable.prototype.deliver =function(data) { for (var i in this.observers) { this.observers[i].func.call(this.observers[i].context, data); //функция теперь вызывается в нужном контексте } } Function.prototype.subscribe = function(observable, context) { var ctx = context || this; //если контекст вызова не задан, то контекстом считается this «по-умолчанию», то есть текущая функция var observer = { //теперь наблюдатель будет сообщать, в каком контексте нужно вызвать функцию context: ctx, func: this } observable.observers.push(observer); return this; }
Таким образом, чтобы заработал крайний пример, нужно всего лишь указать в подписке контекст вызова — экземпляр Logger'а:
var logger = new Logger("console"); logger.write.subscribe(c.onChange, logger);
It works! А я надеюсь, этот маленький фокус кому-то пригодится.

