前言
上节我们一块学习了工厂相关的设计模式,可以方便的帮我们创建类或者实例。本节我们换个角度,不关心产出什么实例,我们来研究一下实例生产的过程。
这里有个问题,为什么要关注生产过程,我们要的不都是最后的实例对象吗?话是没错,但是有些实例太复杂了,我们需要一步一步划分好功能,依次创建。才能完成最终对象的创建。
举个例子,我们现在想要一套房子,房子是一个对象没错,我们还要关心这个房子有没有卧室,厨房怎么样,客厅大不大等等。那么这些关注点,都是需要我们创建的。
一、建造者模式
关注建造细节,下面我们创建一个简历的构造函数。
//创建人类
var Human = function(param) {
//技能
this.skill = param && param.skill || '保密';
//兴趣爱好
this.hobby = param && param.hobby || '保密';
}
//类人原型方法
Human.prototype = {
getSkill: function() {
renturn this.skill;
},
getHobby: function() {
renturn this.hobby;
}
}
//实例化姓名类
var Named = fucntion(name) {
var that = this;
//构造器
//构造函数解析姓名的姓与名
(function(name, that) {
if(name.indexOf(' ') > -1) {
that.firstName = name.slice(0, name.indexOf(' '));
that.secondName = name.slice(name.indexOf(' '));
}
}) (name, that);
}
//实例化职位类
var Work = function(work) {
var that = this;
//构造器
//构造函数中通过传入的职位特征来设置相应职位以及描述
(function(work, that) => {
switch(work) {
case 'code':
that.work = '工程师';
that.workDescript = '沉迷编程无法自拔';
break;
case 'UI':
case 'UE':
that.work = '设计师';
that.workDescript = '设计是一种艺术';
break;
case 'teach':
that.work = '教师';
that.workDescript = '我分享,我快乐';
break;
default :
that.work = work;
that.workDescript = '对不起,我们还不清楚职位的相关描述';
}
}) (work, that);
}
//更换期望的职位
Work.prototype.changeWork = function(work) {
this.work = work;
}
//更改对职位的描述
Work.prototype.changeDescript = function(setence) {
this.workDescript = setence;
}
我们的最终目的是创建一位应聘者,创建过程需要用到上面抽象的三个类,这时候需要一个建造者类,在建造者类中,我们需要通过对三个类的组合调用,创建出一个完整的应聘者对象。
/******
* 应聘者建造者
* 参数 name : 姓名(全名)
* 参数 work : 期望职位
*/
var Person = function(name, work) {
//创建应聘者缓存对象
var _person = new Human();
//创建应聘者姓名解析对象
_person.name = new Named(name);
//创建应聘者期望职位
_person.work = new Work(work);
return _person;
}
上面,我们通过三部分来创建一位应聘者。
var person = new Person('xiao ming', 'code');
建造者模式与工厂最大的不同,就是建造者会参与具体的创建,干涉建造细节,生成的对象,也更复杂。
二、原型模式
接下来要介绍的设计模式,是整个语言的灵魂,原型模式。
原型模式指的是:用原型实例指向创建对象的类,使用于创建新的对象的类共享原型对象的属性以及方法。
举个例子,我们现在创建一个轮播图对象,不同的轮播图效果不一样,有左右滑动的,还有上下滑动的,另外渐变的轮播图也有。
//图片轮播类
var LoopImages = function(imgArr, container) {
this.imagesArray = imgArr; //轮播图片数组
this.container= container; //轮播图片容器
this.createImage = function() {} //创建轮播图片
this.changeImage = function() {} //切换下一张图片
}
//上下滑动切换类
var SlideLoopImg = function(imgArr, container) {
//构造函数继承图片轮播类
LoopImages.call(this, imgArr, container);
//重写继承的切换下一张图片方法
this.changeImage = function() {
console.log('SlideLoopImg changeImage function');
}
}
//渐隐切换类
var FadeLoopImg = function(imgArr, container, arrow) {
LoopImages.call(this, imgArr, container);
//切换箭头私有变量
this.arrow = arrow;
this.changeImage = function() {
console.log('FadeLoopImg changeImage function');
}
}
创建一个显隐轮播图片来测试
var fadeImg = new FadeLoopImg([
'01.jpg',
'02.jpg',
'03.jpg',
'04.jpg'
], 'slide', [
'left.jpg',
'right.jpg'
])
fadeImg.changeImage();
上面代码已经实现了我们需要的功能,但是还有一部分可以优化,比如 LoopImage 作为基类,始终是要被子类继承的,那么把一些方法写到父类的构造函数里就会存在一部分性能问题,因为每次子类继承都要创建一次父类,如果父类里面有很多耗时操作,那么每次创建都会有很大的性能开销,这时候如果把这些耗时操作放到父类的原型上,那么就会节约一大部分性能。
//图片轮播类
var LoopImages = function(imgArr, container) {
this.imagesArray = imgArr; //轮播图片数组
this.container= container; //轮播图片容器
//下面两部分我们移到原型当中,这样就不会每次实例化父类的时候都执行这些耗时操作了
//this.createImage = function() {} //创建轮播图片
//this.changeImage = function() {} //切换下一张图片
}
LoopImages.prototype = {
//创建轮播图片
createImage: function() {
console.log('LoopImages createImage function');
} ,
//切换下一张图片
changeImage : function() {
console.log('LoopImages changeImage function');
}
}
//上下滑动切换类
var SlideLoopImg = function(imgArr, container) {
//构造函数继承图片轮播类
LoopImages.call(this, imgArr, container);
}
SlideLoopImg.prototype = new LoopImages();
//重写继承的切换下一张图片
SlideLoopImg.prototype.changeImage = function() {
console.log('SlideLoopImg changeImage function');
}
//渐隐切换类
var FadeLoopImg = function(imgArr, container, arrow) {
LoopImages.call(this, imgArr, container);
//切换箭头私有变量
this.arrow = arrow;
}
FadeLoopImg.prototype = new LoopImages();
FadeLoopImg.prototype.changeImage = function() {
console.log('FadeLoopImg changeImage function');
}
原型对象还有一个特点,由于原型对象是一个共享的对象,那么不管父类的实例还是子类的实例,都是对它的指向引用,如果原型对象进行扩展,那么无论是子类还是父类,都会继承下来。所以在扩展的时候,一定要慎重。
LoopImages.prototype.getImageLength = function() {
return this.imagesArray.length;
}
FadeLoopImg.prototype.getContainer = function() {
return this.getContainer ;
}
console.log(fadeImg.getImageLength);
console.log(fadeImg.getContainer);
三、单例模式
接下来一起学习最常用的一种模式,一个人的狂欢。单例模式只允许实例化一次对象类。这种模式的出现,是为了更好的管理我们书写的方法,比如 A 写了一个 getElement 的方法, B 也想写,但是命名又不能一样,这就需要我们对自己的方法进行整理归类,比如我们定义一个自己的命名空间 MySelf 。
var Myself = {
getElement: function(id) {
return document.getElementById(id)
},
changeType: function(id, key, value) {
this.getElement(id).style[key] = value;
}
}
上面的代码,就是我们自己的小型代码库,任何我们用到的方法都可以放到这个里面去,当我们想要使用的时候,通过 Myself.getElement 就可以。有了 Myself ,这个命名空间,我们就不用担心会和别人的命名冲突。 JQuery 就是前辈们的一个命名空间,里面有着很多方便我们使用的方法。当我们整理的比较好的时候,也可以发布自己的代码库供别人使用。
单例模式妙用
单例模式还有一个妙用,就是用来管理静态变量。由于在es6之前,JS 中并没有 static 这个关键字,所以是没有静态变量的,但是我们可以手动实现静态变量的功能。
所谓静态变量,就是指那些,可以访问,但是不能修改的变量。
var Conf = (
//私有变量
var conf = {
MAX_NUM: 100,
MIN_NUM: 1,
COUNT: 1000
}
//返回取值器方法
return {
//取值器方法
get: function(name) {
return conf[name] ? conf[name] : null;
}
}
)();
var count = Count.get('COUNT');
console.log(count); //1000
惰性单例
有时候单例对象需要延迟创建,所以还有一种延迟创建的单例模式。
//惰性载入单例
var = LazySingle = (function() {
//单例实例引用
var _instance = null;
//单例
function Single() {
/*这里定义私有属性和方法*/
return {
publicMethod: function() {},
publicProperty: '10'
}
}
//获取单例对象接口
return function() {
//如果为创建单例,则创建单例
if(!_instance) {
_instance = Single();
}
//返回单例
return _instance;
}
})();
console.log(LazySingle().publicProperty) //1.0
到这里,我们已经介绍完了所有关于“创”,“建”,类型的设计模式,从最开始的工厂相关模式,到后面的建造者,原型,单例,这部分包含的功能可以涵盖我们代码的大部分区域,各位同学自己在编码的时候,可以多试试每种模式,我个人的建议是,先按住一个,然后往死里用,合适不合适都这样用,等到什么时候快用吐了,你就真的掌握这种思想了。