Skip to content

Instantly share code, notes, and snippets.

@crossai-2033
Last active December 19, 2015 17:48
Show Gist options
  • Save crossai-2033/5993952 to your computer and use it in GitHub Desktop.
Save crossai-2033/5993952 to your computer and use it in GitHub Desktop.
适配器和插件架构的设计模式
var Lawnchair = function(options, callback) {
// 确保Lawnchair被作为contructor调用
if (!(this instanceof Lawnchair)) return new Lawnchair(options, callback);
// lawnchair依赖JSON
if (!JSON) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.';
// options是可选的,但callback是必须的
if (arguments.length <= 2 && arguments.length > 0) {
callback = (typeof arguments[0] === 'function') ? arguments[0] : arguments[1];
options = (typeof arguments[0] === 'function') ? {} : arguments[0];
} else {
throw 'Incorrect # of ctor args!'
}
// TODO 或许使用pub/sub模式替代?
if (typeof callback !== 'function') throw 'No callback was provided';
// 默认配置
this.record = options.record || 'record' // default for record
this.name = options.name || 'records' // 存储结构默认的名字
// mixin第一个有效的adapter(适配器)
var adapter;
// 如果我们想要使用的adapter已经传入进来
if (options.adapter) {
for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) {
if (Lawnchair.adapters[i].adapter === options.adapter) {
adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined;
break;
}
}
}
// 否则就找到当前环境中的第一个有效的适配器
else {
for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) {
adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined;
if (adapter) break;
}
}
// 加载适配器失败,异常
if (!adapter) throw 'No valid adapter.';
// mixin the adapter
for (var j in adapter)
this[j] = adapter[j]
// 初始化mixin进来的plugin
for (var i = 0, l = Lawnchair.plugins.length; i < l; i++) {
Lawnchair.plugins[i].call(this);
}
// 初始化适配器
this.init(options, callback);
}
Lawnchair.adapters = [];
/**
* 做一个适配器的队列
* ===
* 确保适配器拥有统一的接口
*/
Lawnchair.adapter = function(id, obj) {
// 添加适配器的id给适配器对象obj
// 为了拥有清晰的SDL所以使用了比较丑陋的设计
obj['adapter'] = id;
// 作为一个lawnchair适配器应该具备的方法
var implementing = 'adapter valid init keys save batch get exists all remove nuke'.split(' '),
indexOf = this.prototype.indexOf;
// mix in the adapter
for (var i in obj) {
if (indexOf(implementing, i) === -1) throw 'Invalid adapter! Nostandard method: ' + i;
}
// 如果我们确保这个适配器是有效的,那么我们就添加到有线队列中
Lawnchair.adapters.splice(0, 0, obj);
};
/**
* 给插件机制的简单扩展
* ===
* 如果能够找到init方法被注册了,那么就直接调用init方法启动插件
* - 遍历熟悉可以使用hasOwnProp,但是应该没人去破坏pro
*/
Lawnchair.plugin = function(obj) {
for (var i in obj) {
i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i];
}
};
/**
* helpers辅助
*/
Lawnchair.prototype = {
isArray: Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]'; }
// 低版本的shim
indexOf: function(ary, item, i, l) {
if (ary.indexOf) return ary.indexOf(item);
for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i;
return -1;
},
// 强大的简洁字符串形式函数回调,无耻的从dojo抄袭的
lambda: function(callback) {
return this.fn(this.record, callback);
},
fn: function(name, callback) {
return typeof callback === 'string' ? new Function(name, callback) : callback;
},
// 唯一的标识符
// TODO 研究一个更短小的UUID
uuid: function() {
var S4 = function() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
}
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
},
// 一个经典的遍历器
each: function(callback) {
var cb = this.lambda(callback);
if (this.__results) {
for (var i = 0, l = this.__results.length; i < l; i++) cb.call(this, this.__results[i], i);
}
else {
this.all(function(r) {
for(var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i);
});
}
return this;
}
};
/**
* dom storage adapter
* ===
* 链式操作 valid, keys除外
*/
Lawnchair.adapter('dom', (function() {
var storage = window.localStorage;
// indexer是一个需要保持顺序索引的keys的包装
var indexer = function(name) {
return {
key: name + '._index_',
// return the index
all: function() {
var a = storage.getItem(this.key);
if (a) {
a = JSON.parse(a)
}
if (a === null) storage.setItem(this.key, JSON.stringify([])); // lazy init
return JSON.parse(storage.getItem(this.key));
},
// 添加一个key到索引中
add: function(key) {
var a = this.all();
a.push(key);
storage.setItem(this.key, JSON.stringify(a));
},
// 从索引中删除一个key
del: function(key) {
var a = this.all(), r = [];
// 使用低效的排除法
for (var i = 0, l = a.length; i < l; i++) {
if (a[i] != key) r.push(a[i]);
}
storage.setItem(this.key, JSON.stringify(r));
},
// return index for a key
find: function(key) {
var a = this.all();
for (var i = 0, l = a.length; i < l; i++) {
if (key === a[i]) return i;
}
return false;
}
}
};
// adapter api
return {
// 确保当前环境由localStorage
valid: function() {
return !!storage;
},
init: function(options, callback) {
this.indexer = indexer(this.name);
if (callback) this.fn(this.name, callback).call(this, this);
},
save: function(obj, callback) {
var key = obj.key ? this.name + '.' + obj.key : this.name + '.' + this.uuid();
// 如果目前的key在索引中还不存在,那么就添加进去
if (this.indexer.find(key) === false) this.indexer.add(key);
delete obj.key;
storage.setItem(key, JSON.stringify(obj));
obj.key = key.slice(this.name.length + 1);
if (callback) {
this.lambda(callback).call(this, obj);
}
return this;
},
batch: function(ary, callback) {
var saved = [];
for (var i = 0, l = ary.length; i < l; i++) {
this.save(ary[i], function(r) {
saved.push(r);
});
}
if (callback) this.lambda(callback).call(this, saved);
return this;
},
keys: function(callback) {
if (callback) {
var name = this.name,
keys = this.indexer.all().map(function(r) { return r.replace(name + '.', '')});
this.fn('keys', callback).call(this, keys);
}
},
get: function(key, callback) {
if (this.isArray(key)) {
var r = [];
for (var i = 0, l = keys.length; i < l; i++) {
var k = this.name + '.' + key[i];
var obj = storage.getItem(k);
if (obj) {
obj.key = key[i];
r.push(obj);
}
}
if (callback) this.lambda(callback).call(this, r);
} else {
var key = this.name + '.' + key;
var obj = storage.getItem(key);
if (obj) {
obj = JSON.parse(obj);
obj.key = key;
}
if (callback) this.lambda(callback).call(this, obj);
}
return this;
},
exists: function(key, cb) {
var exists = this.indexer.find(this.name + '.' + key) === false ? false : true;
this.lambda(cb).call(this, exists);
return this;
},
all: function(callback) {
var idx = this.indexer.all(),
r = [],
o,
k;
for(var i = 0, l = idx.length; i < l; i++) {
k = idx[i];
o = JSON.parse(storage.getItem(k));
o.key = k.replace(this.name + '.', '');
r.push(o);
}
if (callback) this.fn(this.name, callback).call(this, r);
return this;
},
remove: function(keyOrObj, callback) {
var key = this.name + '.' + ((keyOrObj.key) ? keyOrObj.key : keyOrObj);
this.indexer.del(key);
storage.removeItem(key);
if (callback) this.lambda(callback).call(this);
return this;
},
nuke: function(callback) {
this.all(function(r) {
for(var i = 0, l = r.length; i < l; i++) {
this.remove(r[i]);
}
if (callback) this.lambda(callback).call(this);
});
return this;
}
}
})());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment