Skip to content

Instantly share code, notes, and snippets.

@tetsuharuohzeki
Last active January 4, 2016 18:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tetsuharuohzeki/8661419 to your computer and use it in GitHub Desktop.
Save tetsuharuohzeki/8661419 to your computer and use it in GitHub Desktop.
/* vim: set filetype=typescript shiftwidth=4 tabstop=4 expandtab: */
/*
* @license MIT License
*
* Copyright (c) 2014 Tetsuharu OHZEKI <saneyuki.snyk@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
// for TypeScript 0.9.5
"use strict";
module TypedObserver {
// FIXME: for ~IE8
var useFreeze: boolean = !!Object.freeze;
// FIXME: for ~IE8
var useArrayIndexOf: boolean = !!Array.prototype.indexOf;
function polyfillArrayIndexOf<T> (aArray: T[], aTarget: T): number {
for (var i = 0, l = aArray.length; i < l; ++i) {
if (aArray[i] === aTarget) {
return i;
}
}
return -1;
}
/**
* The interface is abstract type to describe a type like hashmap.
*
* XXX:
* This is a tricky usage of TypeSciprt's structural subtyping system.
* This may not have any compatibility to ES6 Map type.
*/
interface AbstractMap<K, V> {}
/**
* The interface of a message broadcasted to an observer.
*/
export interface Msg {
topic: string;
}
// The observer pattern oberver
export interface Observer {
handleMessage(msg: Msg): void;
}
// observer pattern subject
export class Subject {
// The acutual type is a normal 'Object'.
// FIXME: Use ES6 Map
private _map: AbstractMap<string, Observer[]>;
constructor() {
this._map = {};
}
/**
* Notify to observers related to the topic.
*
* This method doesn't ensure that the subject broadcasts
* the message synchronously. You must design your codes
* as it can work async.
*/
notify(aMsg: Msg): void {
var list: Observer[] = this._map[aMsg.topic];
if (!list) {
return;
}
for (var i = 0, l = list.length; i < l; ++i) {
list[i].handleMessage(aMsg);
}
}
/**
* Register the observer to the related topic.
*/
add(aTopic: string, aObserver: Observer): void {
var list: Observer[] = null;
if (this._map.hasOwnProperty(aTopic)) {
list = this._map[aTopic];
}
else {
list = [];
}
// check whether it has been regisetered
var index: number = useArrayIndexOf ?
list.indexOf(aObserver) :
polyfillArrayIndexOf<Observer>(list, aObserver);
var isInList: boolean = index !== -1;
if (isInList) {
return;
}
list.push(aObserver);
this._map[aTopic] = list;
}
/**
* Unregister the observer from the related topic.
*/
remove(aTopic: string, aObserver: Observer): void {
if ( !this._map.hasOwnProperty(aTopic) ) {
return;
}
var list: Observer[] = this._map[aTopic];
var index: number = useArrayIndexOf ?
list.indexOf(aObserver) :
polyfillArrayIndexOf<Observer>(list, aObserver);
if (index === -1) {
return;
}
list.splice(index, 1);
// if the list doesn't have any object,
// this remove the message id related to it.
if (list.length === 0) {
delete this._map[aTopic];
}
}
/**
* Unregister all observers from the related topic.
*/
removeTopic(aTopic: string): void {
if ( !this._map.hasOwnProperty(aTopic) ) {
return;
}
this._removeTopic(aTopic);
}
/**
* Finalize the subject.
* This method removes all topics from the subject.
* You can use this method as a destructor.
*/
destroy(): void {
// FIXME: use Object.keys();
for (var topic in this._map) {
if ( this._map.hasOwnProperty(topic) ) {
this._removeTopic(topic);
}
}
if (useFreeze) {
Object.freeze(this._map);
}
}
private _removeTopic(aTopic: string) : void {
var list: Observer[] = this._map[aTopic];
for (var i: number = 0, l: number = list.length; i < l; ++i) {
list[i] = null;
}
delete this._map[aTopic];
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment