JS设计模式(二):工厂模式、单例模式和适配器模式
软件开发的历史,就是和bug斗争的历史。
前言
「 这个世纪的哲学将会成为下一个世纪的常识 」这是《Linux/Unix设计思想》中提到的。「良好的程序员写出优秀的软件,优秀的程序员“窃取”优秀的软件 」,当然不是鼓励我们去粘贴复制,而是让我们不仅仅是粘贴复制,还要转化成为「 自己的东西 」。 ,这期接着聊。
工厂模式
工厂模式简单的理解就是「 对象创建的封装 」,为什么对new出来的对象进行封装呢?
抽象工厂的概念是为了把一些细节的东西封装起来,让开发者把重心移到该怎么用,而不用过多关注于怎么实现。举一个简单的例子:我们不用考虑系统内核的具体现实,我们只需要考虑编程语言怎么用,代码怎么写。
工厂模式的场景很多,例如这样:
(function(global, factory) {
typeof exports === 'object'
&& typeof module !== 'undefined' ?
module.exports = factory() :
typeof define === 'function'
&& define.amd ? define(factory) :
(global.UploadClient = factory());
}(this, function() {
//...
return {
//...
}
})
我们常常创建一个自调用函数,把全局对象传进去,把我们的工厂挂载到上面,方便我们调用。
如果上面的例子还不明确,我们再看看其他场景:
在jQ中就是用的工厂模式:
//模拟一个jQ的实现
class jQuery {
constructor(selector) {
let dom = [].slice.call(document.querySelectorAll(selector));
let len = dom ? dom.length : 0;
for (let i = 0; i < len; i++) {
this[i] = dom[i];
}
this.length = len;
this.selector = selector || "";
}
append(node) {}
addClass(name) {}
html(data) {}
//...
}
window.$ = function (selector) {
return new jQuery(selector);
};
在react中也用到了工厂模式:
//简单的模拟react中createElement
class Vnode {
constructor(tag, attrs, children) {
//....
}
}
React.createElement = function (tag, attrs, children) {
return new Vnode(tag, attrs, children);
};
工厂模式实现了"构造者"与"创建者"的分离。符合开放封闭原则,只能使用,无法修改。
我们举一个简单的商品与工厂的例子,来用代码实现一次:
class Product {
constructor(name) {
this.name = name;
}
init() {
console.log("init");
}
add() {
console.log("add");
}
}
class Creater {
create(name) {
return new Product(name);
}
}
let creater = new Creater();
let p = creater.create("p1");
p.init();
单例模式
单例模式就是一个类只能有一个实例化对象。通常情况下,我们创建的类,可以实例化很多对象来满足需求,但是有的需求我们只需一个实例化对象,比如购物车,商品有很多,但购物车只能有一个。
在四人帮(GoF)的书里面,单例模式的应用描述如下:
每个类只有一个实例,这个实例必须通过一个广为人知的接口,来被客户访问。
子类如果要扩展这个唯一的实例,客户可以不用修改代码就能使用这个扩展后的实例。
由于JS是动态类型的语言,我们只能规范,如下:使用时,我们不能去new,而是直接调用。
class SingleObject{
login(){
console.log("login ....")
}
}
SingleObject.getInstance=(function(){
let instance;
return function(){
if(!instance){
instance=new SingleObject()
}
return instance
}
})()
let obj1=SingleObject.getInstance();
obj1.login();
let obj2=SingleObject.getInstance();
obj2.login();
console.log(obj1===obj2)
//true
//无法控制,只能靠规范约束
let obj3=new SingleObject();
obj3.login();
使用单例模式的场景还有登录、以及vuex和redux中的store等等。
单例模式符合单一职责原则,只实例化唯一的对象,保证唯一性。
同样单例模式不可避免的带来一些弊端,单例模式更难测试,因为可能有多种多样的问题出现,例如隐藏的依赖关系,很难去创建多个实例,很难清理依赖关系,等等。
适配器模式
适配器模式是将本来不兼容的接口或者类转化为可以通用的方法。
常见的场景就是兼容浏览器的api,比如我们常常封装一些方法来获取样式,但是IE和其他浏览器提供的api是不相同,所以我们需要创建一个公共的方法来处理这些不同平台的接口。
还有一个场景是新需求和旧需求有冲突的情况下,在不改变旧有的功能的基础上,添加新的功能。
比如获取样式:
//兼容IE
function getStyle(ele,attr){
if(window.getComputedStyle){
return window.getComputedStyle(ele,false)[attr];
}else{
return ele.currentStyle[attr];
}
}
举一个简单的例子:比如法国的插头要拿到中国用,但是中国的插座与法国的插头不配,我们需要一个适配器来转化。
class Adaptee {
specificRequest() {
return "法国标准插头";
}
}
class Target {
constructor() {
this.adaptee = new Adaptee();
}
request() {
let info = this.adaptee.specificRequest();
return `${info} ==> 中国标准插头`;
}
}
let target = new Target();
let res = target.request();