前几天我曾分享过几张不那么严谨的思维导图,其中便有关于设计模式的一张:
在很长的一段时间里,我只能记住某几种设计模式,并没有很好的应用。
索性我们就以该图为大纲,讲讲那些我们不经意间使用的设计模式 --- 创建型。
FactoryPattern
通常来说三种设计模式为:
SimpleFactory
)Factorymethod
)Abstractfactory
)其核心就是:
工厂起到的作用就是隐藏了创建实例的复杂度,只需要提供一个接口,简单清晰。--- 摘自《前端面试之道》
而区别则是:
其实从你会用 jQuery
开始,就已经在用工厂模式了:
JavaScript设计模式与实践--工厂模式
jQuery
的 $(selector)
jQuery
中 $('div')
和 new$('div')
哪个好用?很显然直接 $()
最方便 ,这是因为 $()
已经是一个工厂方法了。
class jQuery {
constructor(selector) {
super(selector)
}
// ....
}
window.$ = function(selector) {
return new jQuery(selector)
}
React
的 createElement()
React.createElement()
方法就是一个工厂方法
React.createElement('h1', null, 'Hello World!'),
Vue
的异步组件通过 promise
的方式 resolve
出来一个组件
对应源码:
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
// ...逻辑处理
// async component
let asyncFactory
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
}
SingletonPattern
单例模式是最简单的设计模式之一。用一句大白话来解释就是:
实例一次后处处可用
单例模式的要点有三个:
从具体实现角度来说,就是以下三点:
同样的,它也是我们最早接触的一种设计模式:
多次引用只会使用一个库引用,如 jQuery
, lodash
, moment
等。
Vuex/Redux
全局状态管理 store
Vuex
和Redux
数据保存在单一store
中,Mobx
将数据保存在分散的多个store
中
const store = createStore(reducer)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Vue
中第三方插件的安装首当其冲的就是 Vuex
安装:
let Vue // bind on install
export function install (_Vue) {
if (Vue && _Vue === Vue) {
// 如果发现 Vue 有值,就不重新创建实例了
return
}
Vue = _Vue
applyMixin(Vue)
}
其它也类似,感兴趣的可以去 GitHub
搜索 exportfunctioninstall(Vue)
class SingletonClass {
constructor() {
if (!SingletonClass.instance) {
SingletonClass.instance = this;
}
return SingletonClass.instance;
}
// other things
}
const instance = new SingletonClass();
Object.freeze(instance);
export default instance;
BuilderPattern
、建造者模式主要用于 “分步骤构建一个复杂的对象”
这在其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。
一句话:指挥者分配任务,建造者进行开发,各执其责,稳定在一个大的流程里面去。
建造者模式概念拟物化解读
一位女士要建造一座别墅,需要找来一位包工头,包工头再将具体的任务分配给工人做,做完之后再给女士使用。
jQuery
中的建造者jQuery
中建造者模式体现在:
$( "<div class= "foo">bar</div>" );
$( "<p id="test">foo <em>bar</em></p>").appendTo("body" );
var newParagraph = $( "<p />" ).text( "Hello world" );
$( "<input />" )
.attr({ "type": "text", "id":"sample"});
.appendTo("#container");
下面是 jQuery
源码内部 jQuery.prototype
方法的一个片段,它将从传递给 jQuery()
选择器的标记构建 jQuery
对象。
无论是否 document.createElement
用于创建新元素,对元素(找到或创建)的引用都会注入到返回的对象中,因此 .attr()
可以在其后立即使用其他方法。
// HANDLE: $(html) -> $(array)
if ( match[1] ) {
context = context instanceof jQuery ? context[0] : context;
doc = ( context ? context.ownerDocument || context : document );
//如果传入的是单个字符串,并且是单个标记
//只需执行createElement并跳过其余部分
ret = rsingleTag.exec( selector );
if ( ret ) {
if ( jQuery.isPlainObject( context ) ) {
selector = [ document.createElement( ret[1] ) ];
jQuery.fn.attr.call( selector, context, true );
} else {
selector = [ doc.createElement( ret[1] ) ];
}
} else {
ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
selector = ( ret.cacheable ? jQuery.clone(ret.fragment)
: ret.fragment ).childNodes;
}
return jQuery.merge( this, selector );
本质上,建造者模式的目标是减少构造函数所用的参数数量,并提供向对象添加灵活的行为方法。
// 使用建造者模式之前
const person1 = new Person('Peter', 26, true, 40074986, 4, 2);
// 使用建造者模式之后
const person1 = new Person();
person1
.name('Peter')
.age(26)
.member(true)
.phone(40074986)
.children(4)
.cars(2);
ES6
中的建造者模式我们来假设一个商品录入系统的业务场景,有四个必填信息,分别是:名称,价格,分类。该 build
方法将返回最终的 JavaScript
对象。
// 书籍建造者类
class ProductBuilder {
constructor() {
this.name = '';
this.price = 0;
this.category = '';
}
withName(name) {
this.name = name
return this
}
withPrice(price) {
this.price = price
return this
}
withCategory(category) {
this.category = category
return this
}
build() {
return {
name: this.name,
price: this.price,
category: this.category,
}
}
}
console.log(
new ProductBuilder()
.withName('《哈利·波特》')
.withCategory('book')
.withPrice('29.9')
.build()
虽然只有三个属性,我们的构建器已经相当大,并且需要很多 withers
。
构建器的大小随字段数量线性增长。我们有太多的 withxxxx
方法。我们其实可以自动创建这类 withxxxx
方法以简化代码。
class ProductBuilder {
constructor() {
this.name = ''
this.price = ''
this.category = 'other'
// 为每个属性生成`wither`
Object.keys(this).forEach(key => {
const witherName = `with${key.substring(0, 1).toUpperCase()}${key.substring(1)}`
this[witherName] = value => {
this[key] = value
return this
}
})
}
build() {
// 获取此生成器的所有非函数属性的数组
const keysNoWithers = Object.keys(this).filter(key => typeof this[key] !== 'function')
return keysNoWithers.reduce((returnValue, key) => {
return {
...returnValue,
[key]: this[key],
}
}, {})
}
}
console.log(
new ProductBuilder()
.withName('《哈利波特》')
.withCategory('book')
.build()
)
我们将所有的建造方法withxxxx在constructor调用时自动被创建,这里我们使用了一些ES6的新语法:Object.keys获取对象属性数组,...的合并对象的语法
最终我们得到了一种声明式(易于理解)的方法,且可以动态添加属性的建造者模式。
当你有许多建造者时,我们可以轻松地将其广义部分提取到一个通用的父类中,从而可以非常轻松地创建新的建造者。
class BaseBuilder {
init() {
Object.keys(this).forEach((key) => {
const witherName = `with${key.substring(0,1).toUpperCase()}${key.substring(1)}`;
this[witherName] = (value) => {
this[key] = value;
return this;
};
});
}
build() {
const keysNoWithers = Object.keys(this).filter((key) => (
typeof this[key] !== 'function'
));
return keysNoWithers.reduce((returnValue, key) => {
return {
...returnValue,
[key]: this[key]
};
}, {});
}
}
然后就可以创建多个建造者了:
class ProductBuilder extends BaseBuilder {
constructor() {
super();
this.name = '《前端劝退秘诀》';
this.price = 9.99;
this.category = 'other';
super.init();
}
}
可以看出,建造者模式的使用有且只适合创建极为复杂的对象。在前端的实际业务中,在没有这类极为复杂的对象的创建时,还是应该直接使用对象字面或工厂模式等方式创建对象。
prototype
...再讲会被砍死吧。
原本打算又双叒憋它个一万多字,把所有设计模式写个遍。
但是觉得吧,这样阅读体验其实并不好(主要还是懒,想慢慢写。)
噢对了,现在还有靠谱内推的可以联系我
huab119
454274033@qq.com
需要转载到公众号的喊我加下白名单就行了。