js设计模式:工厂模式

在 Javascript 中, 因为函数的简单性、可用性和对外暴露少的特性,所以相对比纯粹的面向对象,使用函数式的方式是首选,特别是在创建对象实力的时候。

相比直接创建一个新的对象,工厂方法包装了一个新实例的创建,将对象的创建从实现中分离出来,从而更好也更灵活地控制对象的创建。

举个例子,当我们需要创建一个音频对象audio的时候,使用关键字new是最直接的方式:

1
const audio = new Audio(name);

但是,这种创建方式会限定我们产生的对象,而工厂模式则提供了更多我们创建对象的选项,并且还不用暴露创建对象的构造函数,避免被继承或者修改:

1
2
3
4
5
6
7
8
9
function createAudio(name) {
if(name.match(/\.wav$/)) {
return new WavAudio(name);
} else if(name.match(/\.mp3$/)) {
return new Mp3Audio(name);
} else {
throw new Exception('没有该类型音频!');
}
}

使用闭包封装

很多人都迷惑为什么总会在笔试面试的时候被问到闭包的知识,因为大多数人对于闭包的使用第一反应可能是生成块作用域,然而即使在ES2015引入块作用域之后,闭包依然还有很多方向的应用,比如在工厂模式中实现私有变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createAnimal(species) {
const privateProps = {};
const animal = {
setSpecies: species => {
if(!specis) throw new Error('请输入该动物的物种1');
privateProps.species = species;
}
getSpecies: () => {
return privateProps.species;
}
};
animal.setSpecies(species);
return animal;
}

这样就可以很好地将封装私有变量了,通过闭包可以让私有变量只能通过gettersetter来访问和修改。

使用工厂模式构建 Debugger

工厂模式有一个特别常用的场景,就是根据不同的环境,输出不同的对象,接下来,我们就自己构建一个简单的debugger,可以在开发模式调试代码,并且在生产模式不输出调试结果:

1
2
3
4
5
6
7
8
9
10
11
function createDebugger() {
if(process.env.NODE_ENV === 'development') {
return console;
} else if(process.env.NODE_ENV === 'production') {
const deb = {};
Object.keys(console).forEach(key => {
deb[key] = () => {};
});
return deb;
}
}

这样我们就能在不同的生产环境中初始化不同的对象,从而实现不一样的结果。

可组合的工厂函数

可组合的工厂函数 是一类特殊的工厂函数,它们可以被组合到一起来构建一个功能增强的工厂函数。当我们要创建从多个源继承一些行为和属性的对象,又不想构建复杂类结构的时候,就可以考虑组合工厂函数。

举个例子,我们每个人都有自己的namesexage等一些基础属性,然后在我们中有着不同的职业:

  • 歌手
  • 画家
  • 程序猿

再从另外一个维度看,又会有各种爱好者:

  • 篮球
  • 游戏
  • 射击

这样就能组合出:

  • 热爱篮球的歌手
  • 热爱游戏的歌手
  • 热爱射击的歌手
  • 等等等等(还有可能喜欢多种项目或是从事多个职业)

所以就需要我们将这些抽象出来的对象组合起来:

1
2
3
4
5
6
7
8
9
10
11
function compose(objList=[]) {
const composedObj = {};
objList.map(obj => {
Object.keys(obj).map(key => {
composedObj[key] = obj[key];
});
});
return composedObj;
}

const shottingSinger = compose([new Singer, new Shotter]);

当然,这只是个示例,少了很多容错的处理,使用 组合工厂函数 推荐一个比较好的库 stampithttps://www.npmjs.com/package/stampit