Технологии

Разное

Как развить глубокие познания в программировании?

Сначала хотел написать о методах, которые описаны в многочисленных статьях. Но пожалуй остановлюсь на интерпретации мудрейшего изречения Йоги Бхаджан: “Если вы хотите чему-то научиться, прочитайте об этом, если вы хотите что-то понять, напишите об этом, если вы хотите освоить что-то, запрограммируйте это”.

Технологии

  • 79 вещей которые должен знать любой программист – этакая “Энциклопедия Юных Сурков” для программистов. Пока собирался написать этот пост, оригинальная ссылка перестала работать. Вот тут есть часть статей из списка
  • Очередная статья про Вебсокеты. Приводится сравнение с Comet-технологией
  • Офигительная js-библиотка SlickGrid для работы с гридами(табличками)
  • Использование Traits в PHP 5.4
  • Storing Date/Times in Databases – как правильно хранить временные метки

Разное

  • Не умеешь кодировать? Воспользуйся Scroll Kit для создания своего сайта
  • Главного дизайнера Apple посвятили в рыцари

Аспектно-ориентированное программирование в PHP

Обычно, чтобы было понятнее, что такое аспектно-ориентированное программирование, приводят пример с логированием. Допустим нам нужно веси лог удалений. Причем, удалений всего и вся. Самый простой способ  это добавить вызов логирующего метода в тех местах где происходит удаление сущности, например, в методе deleteUser(). Разумеется логирование придется добавить во все подобные методы всех сущностей. Во первых это муторно, во вторых нарушает четкое предназначение класса(метод не только удаляет, но еще и записывает лог). Еще один способ этот использование паттерна наблюдателя.

АОП придумана как раз таки для реализации такой сквозной функциональности. Как это реализовать в PHP с помощью фреймворка FLOW3 читайте в статье Aspect-Oriented Programming in PHP .

В двух словах это работает так: при написании метода логирования вы даете указание в какик методах он должен вызываться. Примерно вот так:

1
2
3
4
5
6
7
8
/**
* An aspect that is executed on all methods that start with "delete".
*
* @FLOW3\Before("method(.*->delete.*())")
*/

public function logginMethod() {
// Do some logging here.
}

Ну а фреймворк уже сам позаботится о вставке вашего метода в нужные места.

 
Технологии

  • OAuth 2.0 простым и понятным языком – не свежий, но полезный пост про OAuth 2.0
  • Помните отличную презентацию про HTML5? Можно забабахать презентацию не хуже с помощью LakTEK. На первый взгляд всё просто.
  • В хроме появился новый атрибут download у тега A. Атрибут указывает на то, что ресурс указанный в ссылке нужно предложить скачать, а не пытаться открыть его в браузере
  • HTML5 Audio — The State of Play -подробный разбор: методы, атрибуты, события…вплоть до настройки Апача, для корректной отдачи аудио-файлов.

PHP

  • XDebug исполнилось 10 лет! Этому событию посвящен новый релиз 2.2.0.
  • Если вы переживаете за качество вашего php-кода, обратите внимание на инструменты и представленные на сайте The PHP Quality Assurance Toolchain
  • Mobile_Detect – облегченный класс для обнаружения мобильных устройств.

Разное

RESTful API Server – Делай все правильно(1 часть)

Это перевод статьи RESTful API Server – Doing it the right way (Part 1)

В 2007 Стив Джобс назвал iPhone революцией в индустрии высоких технологий, изменяющий способы работы и ведения бизнеса. Сейчас, в 2012, все больше и больше сайтов предлагают нативные  клиенты для iOS и Android в качестве фронд-энд-приложений. Не все стартапы имеют финансирование для разработки приложения в добавок к основному продукту. Для увеличения скорости развития их продукта они предлагают публичный API для разработчиков, которые могут использовать его для написания приложений. Twitter, вероятно, был первой такой компанией и теперь все больше и больше компаний  последовали такой же стратегии, действительно отличным путем развития экосистемы вокруг своего продукта.

Стартапы полны преобразований. Если ваш код не может переориентироваться, вы проиграете. Взяв перерыв или еще на старте, серверный код достаточно гибок, что бы адаптироваться к потребностям бизнеса. Успешными стартапами являются не те у которых присутствует хорошая идея, а те, которые реализуют ее.  Успех стартапа зависит от успеха его продукта, пусть это iOS-приложение или его сервис или его API. За последние 3 года я работал над разнообразными iOS-приложениями(в основном для стартапов), использующие веб-сервисы и в этой статье, я попробовал собрать знания и показать вам лучшие практики, чтобы вы могли применить их при разработке RESTful API. Хороший RESTful API который не сопротивляется изменениям.

Целевая аудитория

Этот пост предназначен для читателей, имеющих средние и продвинутые знания о разработке RESTful API и имеющих простые знания о любом объектно-ориентированном(или функциональном) серверном языке, таком как Java/Ruby/Scala(примечание: я намеренно игнорирую PHP)

Структура и организация

Эта часть статьи довольно детальна: в первой части объясняются основы REST, во второй – документирование и управление версиями вашего API. Первая часть для новичков. Вторая – для профи. Я знаю, вы профи. Так что вот ссылка  к разделу о документировании API, можно перейти прямо сейчас. Возможно это то место, с которого вы начнете читать, если решите, что не нужно тратить много времени на основы.

RESTfull-ограничения

Сервер RESTful это тот, который соответствует REST-ограничениям. Вот ссылка на статью в Википедии. Не только при разработке API, которым будут пользоваться преимущественно мобильные устройства, но и при поддержке и развитии, нужно понимать три основных ограничения. Позвольте объяснить.

Безгражданство

Первое ограничение – это безгражданство. Проще говоря, RESTful-сервер ничего не должен знать о клиенте. Клиент, с другой стороны, может поддерживать состояние контекста сервера. Другими словами, вы не должны делать так, что бы сервер помнил о состоянии мобильного устройства, использующего API.

Представим, что ваш стартап это “следующий Фэйсбук”. Хороший пример, того где разработчик может сделать ошибку, это предоставление интерфейса API, который позволит мобильному устройству взять последний элемент потока(говоря о фиде Фейсбука). API обычно возвращает новые элементы, которые появились после последнего считывания. Это ведь разумно, неправда ли? Вы так “оптимизируете”  передачу данных между клиентом и сервером, не так ли? И это неправильно.

Что пойдет не так в этом случае? Когда пользователь пользуется вашим сервисом с двух или трех устройств и одно устройство установило статус последнего прочтенного элемента, то другие устройства уже не смогут получить те элементы которые получило первое устройство.

Данные, возвращаемые  для конкретного вызова API, не должны зависить от вызовов которые были сделаны раннее чем этот.

Верный путь оптимизации, это передача штампа времени, когда был последний вызов API(/feed?lastFeed=20120228). Есть и другой способ, более стандартный, использование заголовка  HTTP Modified Since. Но пока это пропустим. Рассмотрим этот способ во второй части статьи.

Кешируемая и многоуровневая архитектура

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

Клиент-серверное разделение интересов и единый интерфейс

RESTful-сервер должен абстрагироваться и скрывать как можно больше деталей реализации от клиента. Т.е. клиент не должен знать о том какая база данных используется на сервере и сколько баланщировщиков нагрузки сейчас активно или другие подобные вещи. Поддержание хорошоге разделения интересов помогает в расширении, когда продукт становится “вирусным”.

Это вероятно три наиболее важных ограничения, которые вы должны помнить, когда разрабатываете RESTful-сервер.

REST-запросы и четыре HTTP-метода

  • GET
  • POST
  • PUT
  • DELETE

Кэшируемое ограничение и GET-запросы

Ключевая идея – это то, что GET-метод не изменяет состояние сервера. Это означает, что ваши запросы могут быть закешированы на любом промежуточном прокси-сервере(для сокращения нагрузки). Как разработчик сервера вы не должны подвергать изменению вашу базу данных. Это противоречит философии RESTful, второму ограничению, о котором я говорил ранее. Ваш “GET” метод даже не должен делать записи в лог и не должен сохранять время последнего обращения к сервису. Если вы вносите изменения в базу данных, то это должно всегда происходить только в методах POST/PUT.

POST или PUT

В спецификации HTTP 1.1 сказано, что PUT – идемпотент((от лат. idem – такой же и potens – сильный, то есть имеющий такую же силу). Это означает, что клиент может выполнить множество PUT-запросов к одному и тому же URI и это не должно создать/изменить дублирующие записи в базе.
Операции присваивания, являются хорошим примером идемпотентных операции.

String userId = this.request["USER_ID"];

Даже если выполнить эту операцию два или три раза, ничего вредного не произойдет.
POST же, с другой стороны, не идемпотичен. Это как оператор прибавления. Вы должны использовать POST или PUT основываясь на то, какой метод должен быть: идемпотентный или нет. Если клиент “знает” URL объекта который может быть создан, то нужно использовать метод PUT. Если клиент знает URL создателя/фабрики, то нужно использовать POST.

PUT www.example.com/post/1234

Используйте PUT, если клиент знает URI, который будет создан в результате вызова. Даже если клиент вызовет PUT -метод несколько раз, ничего вредного не произойдет и не создадутся дублирующие записи.

POST www.example.com/createpost

Используйте POST-метод, если сервер создаст уникальный ключ и вернет результат обратно клиенту. Дублирующиеся запись появятся, если позднее будет запрос с такими же параметрами.

Прочтите этот ответ на Stackoverflow, чтобы узнать больше.

DELETE

DELETE – прямолинеен. Это опять идемпотент, как и PUT, и должен использоваться для удаления записи, если она существует.

REST Responses(ответы)

Ответы вашего RESTfull-сервера могут быть в XML или JSON формате. Лично я предпочитаю JSON, потому что он проще, и размер передаваемых данных меньше, чем при использовании XML-формата. Разница может быть в несколько сотен килобайт, но используя сети 3G и непостоянное соединение мобильного устройства, эти несколько сотен килобайт могут иметь огромное значение при загрузке ответных данных.

Аутентификация

Аутентификация должна проводится по протоколу https и клиент должен отправлять пароль зашифрованный криптографическим алгоритмом. Получение sha1 хэш NSString в Objective-C является довольно прямолинейным и следующий код иллюстрирует это.

- (NSString *) sha1
{
	const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding];
	NSData *data = [NSData dataWithBytes:cstr length:self.length];

	uint8_t digest[CC_SHA1_DIGEST_LENGTH];

	CC_SHA1(data.bytes, data.length, digest);

	NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];

	for(int i = 0; i <; CC_SHA1_DIGEST_LENGTH; i++)
		[output appendFormat:@"%02x", digest[i]];

	return output;
}

Сервер должен сравнить зашифрованный пароль с зашифрованным паролем, сохраненным на сервере ранее. В любом случае вы не должны передавать пароль в открытом виде между клиентом и сервером. В этом правиле нет исключений. В день, когда пользователь придет и узнает, что вы храните пароли в открытом виде, будет днем смерти вашего стартапа. Потерянное доверие может уже никогда не вернуться.

Спецификация RFC 2617 говорит о двух путях авторизации с HTTP-сервером. Первый это Basic Access Authentication, второй – Digest Authentication. Для внутреннего мобильного клиента использование Basic или Digest-авторизации вполне достаточно и в большинстве серверных языков, такой механизм авторизация реализован.

Если вы планируете сделать ваш API публичным, то вам следует рассмотреть oAuth, а лучше oAuth 2.0. oAuth позволяет вашим конечным пользователям размещать контент, созданный вашим приложением, с другим сторонним поставщиком обработки ключей(логин/пароль). oAuth также позволяет пользователю полностью контролировать, что сделать публичным и какие права могут быть разрешены приложонию третьей стороной.

Facebook Graph API – это крупнейшая реализация oAuth на сегодняшний день. Используя oAuth, пользователь Фейсбуку может обмениваться фотографиями с приложением третей стороны без раскрытия персональной информации и прочих деталей(логин/пароль). А также пользователь может отменить доступ для приложения третей стороны без изменения пароля.

До сих пор я говорил о основах REST. Теперь давайте погрузимся в “мясо” статьи. В последующих разделах я поговорю о лучших практиках, которым вы должны следовать в документировании, контроле версий и устаревания вашего API.

API документация

В самой худшей документация сервера, которую разработчие может написать это та, которая содержит длинный список конечных точек API, параметры, которые необходимы и соответствующие ответы от сервера. Проблема в том, что слишком сложно делать изменения данных ответа по мере развития продукта, и это становится кошмаром. У меня есть несколько предложений как сделать документацию вашего API так, чтобы разработчик клиента мог лучше понять вас. Со временем, это вам поможет стать разработчиком лучшего сервера.

Документация

Для начала, прежде чем преступить к документированию, я бы порекомендовал  подумать о своем верхнем уровне моделей объектов. Подумайте о действиях, которые должны совершать эти объекты.  Foursquare API документация – это хороший пример для начала.  У них есть набор объектов верхнего уровня, такие как места, пользователи и т.д. Также есть действия которые могут быть выполнены над этими объектами. Как только вы узнаете объекты верхнего  уровня и действия над ними, разбор конечных точек становится простым и более четким.  Например, чтобы “добавить” новое место, вероятнее всего, нужно вызвать метод похожий на  /venues/add

Документируйте каждый объект верхнего уровня. Далее, с помощью этих объектов верхнего уровня, документируйте запросы и ответы, а не примитивно, только исходные типы данных. Вместо того, что бы писать, что возвращается три строки: первая – идентификатор, вторая – имя, третья – описание, напишите, что это API – возвращает модель места.

Документирование параметров запроса

Давайте предположим, что у вас есть API, который, для входа использует, авторизацию Фейсбука. Вызовем api /login

Request
/login
Headers
Authorization: Token XXXXX
User-Agent: MyGreatApp/1.0
Accept: application/json
Accept-Encoding: compress, gzip
Parameters
Encoding type – application/x-www-form-urlencoded
token – “Facebook Auth Token” (mandatory)
profileInfo = “json string containing public profile information from Facebook” (optional)

Здесь profileInfo это  top-level object(объект верхнего уровня). Этого достаточно, т.к. вы уже задокументировали внутреннюю структуру этого объекта. Если ваш сервер использует те же Accept, Accept-Encoding и параметры кодирования, вы можете задокументировать их отдельно, а не повторять их везде.

Документирование параметров ответа

Ответы API должны документироваться основываясь на модели объектов верхнего уровня. Цитируя тот же форсквеерсеий пример, метод  /venue/#venueid# возвращает полную модель места.

Если ваша модель окажется большой или вы захотите снизить нагрузку, рассмотрите возможность создания компактной модели. Вы можете воспользоваться этим для API, которые будут возвращать список моделей объектов. Форскверовские API поступают также. Их поиск API возвращает список “компактных мест

Обмениваетесь идеями, документируя или сообщая другим разработчикам, что Вы возвратите, когда Вы документируете свой API, используя модели объектов. Главный вывод этого раздела – это рассмотрение документации в качестве договора между вами, сервером разработчика и клиентом разработчиков(iOS/Android/Windows Phone/др.)

Причины версионности.

До мобильных приложений, в эру приложений Веб 2.0, версионность API не была проблемой. Клиент(Javascript/AJAX front-end) и сервер разворачивались одновременно, в одно и тоже время. Потребители(ваши заказчики) всегда пользовались последним фронт-энд клиентом доступа к вашей системе. Потому как, ваша компания разрабатывала и клиент и сервер, вы имели полный контроль над тем, как использовать ваш API, и всегда изменяли клиента, если что то изменилось в API на сервере. К сожалению с нативными клиентами это не возможно. Вы можете развернуть API версии 2 и думать, что все будет хорошо, но это “разорвет” старые версии вашего приложения для iOS, даже после обновления его на АппСторе. В конченом итоге это приведет к потере клиентов. Я видел очень много айфонов, в которых, более 100 приложений, ожидают обновления. У вашего приложения есть хорошие шансы стать одним из них. Вы всегда должны быть готовы к версионности API и старению. Но поддерживать ваш API не менее 3 месяцев.

Версионность.

Развертывание серверного кода в другой каталог и используя другие URLы конечных точек автоматически не означает эффективный перенос кода.
Т.е. вместо использования http://example.com/api/v1 проложению нужно будет использовать последнею наилучшую версию 2.0 http://example.com/api/v2

Когда вы производите обновления, вы почти всегда изменяете внутреннюю структуру данных и модели объектов на вашем сервере. Это подразумевает изменения в базе данных(добавление или удаление столбцов). Чтобы внести ясность, предположим, что API вашего “следующего Фейсбука” имеет вызов /feed, который возвращает “Feed”-объект.

Сегодня, в версии 1, ваш “Feed”-объект содержит ссылку на персональную картинку(avatarURL), имя(personName) -текстовое поле(feedEntryText) и временную отметку(timeStamp) новой записи.

Позднее, во второй версии, вы добавляете возможность рекламодателям  продавать свою продукцию. Теперь ваш “Feed”-объект содержит новое поле, скажем, “sourceName”, вместо “personalName”. Теперь ваш “Feed”-объект, новое поле с именем “sourceName”, которое очень уступает человеческому имени в интерфейсе. Т.е. приложение должно отображать “sourceName”, вместо “personName”. Поскольку для отображения в интерфейсе больше не требуется “personName”, когда присутствует «sourceName», вы решили не посылать «personName» когда посылаете «sourceName». Разумнее посылать оба поля: “sourceName” и “personName”. Но, друзья мои, в жизни это не всегда не так просто. Как разработчик, вы не можете отслеживать все изменения, которые когда-либо были сделаны для каждого объекта модели в своем классе. Это просто не эффективно и через 6 месяцев, вы почти забудите, почему что-то было добавлено код.

Оглядываясь назад, в веб 2.0, это не было вообще проблемой. Фронт-энед на ява скрипте обновлялся сразу же после изменения API. Однако, IOS приложения будут отключены в отличие от веб-приложений. Только пользователь может его обновить.

Я могу предложить очень элегантное разрешение этой ситуации.

Парадигма версионности URL

Во-первых, различайте несколько версий используемых урлов.

http://api.example.com/v1/feeds будет потребляться первой версией приложения для iOS, и
http://api.example.com/v2/feeds – второй версией приложения.

Хотя это звучит хорошо, вы не должны дублировать разрабатываемый вами код, после каждого изменения, затрагиваемого выходные данные. Я рекомендую это делать, только при больших изменениях. Для небольших изменений, рассмотрите версионность ваших моделей.

Парадигма версионности модели

Ранее я показал как документировать модели. Рассматривайте эту документацию как договорное соглашение между серверными и клиентскими разработчиками. Никогда не следует вносить изменения в модель, без изменения версии. Это значит, что в нашем предыдущем примере, должно быть две модели – Feed1 и Feed2. Feed2 имеет sourceName и выводит sourceName и при наличии sourceName удаляет personName. находится в Feed2 и выводит sourceName и при наличии sourceName удаляет personName. Feed1 ведет себя по прежнему так, как это было задокументировано.

В коде исполнение запроса будет выглядеть примерно так:

Вы должны перенести код создания экземпляра в класс фабричный метод. Код должен выглядеть примерно так:

Feed myFeedObject = Feed.createFeedObject("1.0");
myFeedObject.populateWithDBObject(FeedDao* feedDaoObject);

Где 1.0 или 2.0 определяется в в контроллере из строки UserAgent.

Добавление:
Вместо того, чтобы зависеть от номера версии в строке UserAgent, клиент должен отправить номер версии в заголовке Accept.

Поэтому вместо отправки

Accept: application/json

вы должны отправить

Accept: application/myservice.1.0+json

Таким образом, у вас есть возможность запросить другой вариант ответа на каждый объект RES-ресурса, который вы запрашиваете.
Спасибо читателям “hacker news”, которые послали мне это.

Контроллер просит метод  Feed-фабрики создать нужный feed-объект, на основе входящего запроса(все запросы в UserAgent должны выглядят как AppName/1.0) и версию клиента. При такой реализации серверного кода, *любые* изменения будут простыми.  Изменяя серверную часть без нарушения существующих договоров покажется легким бризом. Просто создайте новую модель, изменив фабричный метод, который создаст новую версию модели и вы продвинетесь вперед!

С такой архитектурой модель места, для приложений 1-ой версии и 2-ой все еще могут находиться на том же сервере. Ваш контроллер будет создавать объект 1-ой версии для старых клиентов и 2-ой – для новых.

Устаревание

С парадигмой версионности модели я предположил, что устаревание вашего API упрощается. Это очень важно, когда вы сделаете ваш API публичным.
Когда вы делаете мажорные апдейты, очищайте все фабричные методы ваших моделей, основанных на бизнес-решениях.

Если, после выхода 3-й версии, вы решили больше не поддерживать iOS-приложение 1-ой версии, удалите модели и строки кода, связанные с 1-ой версией API и двигайтесь дальше.

Версионность и устаревание проходят долгий путь в обеспечении долголетия вашей компании и продукта. Они гарантируют достаточную проворность для большинства из поворотных решений. Обычно поворотным изменениям противится именно техническая команда. Такая техника должна разрешить эту проблему.

Кеширование

Следующим важным шагом для повышения производительности, на котором вы должны сосредоточиться при разработки API – это кеширование. Если вы, как и все остальные, думаете о кешировании на стороне клиента, подумайте еще раз. Во второй части я объясню, как поддерживать кэширование, основываясь на стандарты HTTP 1.1.

Обработка ошибок и интернационализация вашего API

Уведомление клиента о произошедших ошибках на сервере, так же важно как и возврат правильных данных. Об этом я расскажу в третей части.

Библиотечки на HTML5

Технологии

  • JS1K – Много демок на JS. Главная цель уместится в 1024 байта. Посмотрите в код )))
  • Backbone.js предоставляет чрезвычайно простые способы для создания модели и представления(MVC) для организации вашего кода логичным образом. Хотя тут нет контролера, что требует MVС-фреймворк. Контролер нужен когда есть взаимодействия клиента и сервера. Когда же нет такого взаимодействия, можно обойтись и без контролера. В Backbone.js реалезован паттерн наблюдателя, что позволяет измененять представления при любых изменениях в модели.
  • Envision.js – библиотека для построения графики и подобной визуализаций. Самом собой на HTML5
  • Rust -новый язык от Mozilla.Визуально похож на языки семейства С, хотя сильно отличается в плане семантики и синтаксиса.
Сервисы
  • Ping Pong Spider Mother – очень забавная игрушка написанная на HTML5
  • Picozu Editor – онлайн графический редактор, апять таки написанный на HTML5
  • Яндекс.Диск — файловое хранилище от Яндекса. До 10 Гбайт бесплатно. Пока, что вход по приглашениям.

Разное

JavaScript шаблоны проектирования: Observer или Наблюдатель

Это вольный перевод статьи JavaScript Design Patterns: Observer

У паттерна наблюдателя очень простая концепция. Наблюдатель(Observer, он же подписчик) подписывается у наблюдаемого объекта(Observable, он же издатель) на события и ждет пока не произойдет что-нибудь интересное. Наблюдатели также могут отказаться от наблюдения. Существует два простых метода у наблюдателя для получения информации о том, что происходит: push(принудительно) и pull(по запросу). В методе push, всякий раз когда что то происходит уведомляется наблюдатель. В методе pull наблюдатель сам проверяет изменилось ли что то у издателя.

Лучше я вам покажу пример. Вы ведь программист, и код понимаете лучше чем английский язык. Начнем с примера push-метода:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var Observable = function() {
    this.subscribers = [];
}
 
Observable.prototype = {
    subscribe: function(callback) {
        // В большинстве случаев вам нужно будет проверять        
        // существует ли подписчик в списке наблюдателей или нет,
        // что бы не добавлять его повторно. Но здесь просто  
        // добавим вызов в список
        this.subscribers.push(callback);
    },
    unsubscribe: function(callback) {
        var i = 0,
            len = this.subscribers.length;
       
        // Пробегаем про всему список если находим нужного нам
        // подписчика, то удаляем его.
        for (; i < len; i++) {
            if (this.subscribers[i] === callback) {
                this.subscribers.splice(i, 1);
                // Если нашли то что искали,
                // продолжать не надо.
                return;
            }
        }
    },
    publish: function(data) {
        var i = 0,
            len = this.subscribers.length;
       
        // Пробегаем по всему списку подписчиков и запускаем
        // функции.
        for (; i < len; i++) {
            this.subscribers[i](data);
        }        
    }
};
 
var Observer = function (data) {
    console.log(data);
}
 
// А вот как все это используется.
observable = new Observable();
observable.subscribe(Observer);
observable.publish('Опубликовано!');

Сделаю несколько заметок про этот пример. Во-первых, все функции связанные с паттерном наблюдателя, реализуются в рамках наблюдаемого объекта. Благодаря гибкости JavaScipt`a, вы могли бы реализовать подписки и отписки в самом наблюдателе, но, я считаю, осуществить это в рамках наблюдаемого объекта(издателя) будет разумнее и понятнее. Еще одним примечательным моментом является то, что наблюдатель – это просто функция, которая используется в качестве функции обратного вызова. В таких языках как Java, наблюдатель должен быть объектом, реализующий специфический интерфейс. Ну и наконец, в пример наблюдаемый объект – это класс, который может использовать сам по себя, хотя гораздо полезнее наследовать его, чтобы сделать другие объекты наблюдаемыми.

Ну а теперь реализация pull-метода. Когда вы используете pull-метод, имеет смыл немного все поменять:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Observable = function() {
    this.status = "construct";
}
Observable.prototype.getStatus = function() {
    return this.status;
}
 
Observer = function() {
    this.subscriptions = [];
}
Observer.prototype = {
    subscribeTo: function(observable) {
        this.subscriptions.push(observable);
    },
    unsubscribeFrom: function(observable) {
        var i = 0,
            len = this.subscriptions.length;
       
        // Пробегаем по всему списку и если находим, то что искали,
        // удаляем
        for (; i < len; i++) {
            if (this.subscriptions[i] === observable) {
                this.subscriptions.splice(i, 1);
                // Не стоит искать дальше,
                // если уже нашли то, что искали
                return;
            }
        }        
    }
    doSomethingIfOk: function() {
        var i = 0;
            len = this.subscriptions.length;
       
        // Пробегаемся по списку подписчиков и определяем
        // изменился ли статус на Ок ,
        // и выполняем то, что нужно подписчику, если это так
        for (; i < len; i++) {
            if (this.subscriptions[i].getStatus() === "ok") {
                // Делаем, что нибудь, потому что стутус
                // такой какой нам нужен
            }
        }
    }
}
 
var observer = new Observer(),
    observable = new Observable();
observer.subscribeTo(observable);
 
// Ничего не произойдет так как статус еще не изменен
observer.doSomethingIfOk();
 
// Изменяем статус на "Ок", чтобы что то произошло
observable.status = "ok";
observer.doSomethingIfOk();

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

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

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