原文出自于本人个人博客网站:https://www.dzyong.com(欢迎访问)
转载请注明来源: 邓占勇的个人博客 –《JavaScript 设计模式(2)—— 多种继承方式的实现及原理》
本文链接地址: https://www.dzyong.com/#/View…
创建型设计模式是一类处理对象创建的设计模式,通过某种方式控制对象的创建来避免基本对象创建时可能导致设计上的问题或增加设计上的复杂度。
接下来我们来讲解下常见的 6 中创建型设计模式: 简单工厂模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式和单例模式 。
简单工厂模式
又称静态工厂模式,由一个工厂对象决定创建某一种产品对象类的实例。主要用来创建同一类对象。
简单工厂模式就好比一个商店,我们需要什么时只需要跟商店说我们需要什么就行,他会帮我们找到所需要的东西。
举一个例子,我们这里声明 3 个基类,每个基类独有他们自己的方法。
// 篮球基类
let Basketball = function(){this.intro = '篮球盛行于美国'}
Basketball.prototype = {getMember: function () {console.log('每个队伍需要 5 名队员')
},
getBallSize: function(){console.log('直径 30cm')
}
}
// 足球基类
let Football = function(){this.intro = '足球盛行于美国'}
Football.prototype = {getMember: function () {console.log('每个队伍需要 11 名队员')
},
getBallSize: function(){console.log('直径 25cm')
}
}
// 网球基类
let Tennis = function(){this.intro = '网球盛行于美国'}
Tennis.prototype = {getMember: function () {console.log('每个队伍需要 1 名队员')
},
getBallSize: function(){console.log('直径 6cm')
}
}
接下来创建我们的商店工厂,在使用时我们只需要 SportsFactory(‘NBA’) 这样就可获得一个篮球对象,当再需要一个足球时同样的方法 SportsFactory(‘wordCap’),这样做的好处是我们不需要每次都去 new 一个新的对象,我们只需要在我们工厂方法中找到我们所需要的东西即可。
// 运动工厂
let SportsFactory = function(name){switch(name){
case 'NBA':
return new Basketball()
case 'wordCap':
return new Football()
case 'FrenchOpen':
return new Tennis()}
}
简单工厂模式的理念就是创建对象,上面的方式是对不同的类进行实例化,这样看起来比较繁琐,也可以用来创建相似对象。把基类相似的地方提取出来,不同的地方进行针对处理即可。
/* 工厂模式 */
let createBook = function(name, time, type){let o = new Object()
o.name = name
o.type = type
o.time = time
o.getName = function(){console.log(this.name)
}
if(type == 'alert'){// 处理差异部分}
if(type == 'prompt'){// 处理差异部分}
if(type == 'confirm'){// 处理差异部分}
return o
}
let book1 = createBook('js book', 2019, 'js')
let book2 = createBook('css book', 2018, 'css')
book1.getName() //'js book'
book2.getName() //'css book'
第一种是通过类实例化对象创建的,第二种是通过创建一个新的对象然后包装增强其属性和功能实现的。
工厂方法模式
通过对产品类的抽象使其创建业务,主要负责用于创建多类产品的实例。
在学习工厂方法模式之前先学习一下安全模式类,安全模式类式可以屏蔽对类的错误使用所造成的错误,举个例子,我们在实例化一个类的时候必须使用 new 关键字来创建,如果缺少了 new 会报错。
let Dome = function(){}
Dome.prototype = {show : function(){console.log('获取成功')
}
}
let d = new Dome()
d.show() // 获取成功
let o = Dome()
o.show() // 工厂方法模式.html:24 Uncaught TypeError: Cannot read property 'show' of undefined
我们使用安全模式类就可以很好地解决这个问题
let Test = function(){if(!(this instanceof Test)){return new Test()
}
}
let t = Dome()
t.show() // 获取成功
工厂方法模式的本意是将实际创建对象的工作推到子类中,这样核心类就成了抽象类。
let Factory = function(content, color){
this.content = content
this.color = color
if(this instanceof Factory)
return this[content](color)
else
return new Factory(content, color)
}
Factory.prototype = {UI: function(){console.log(this.content,this.color)
},
js: function(){console.log(this.content,this.color)
},
JAVA: function(){console.log(this.content,this.color)
},
}
var test = Factory('UI', '白色')
let date = [
{
content: 'UI',
color: '白色',
},
{
content: 'js',
color: '绿色',
},
{
content: 'JAVA',
color: '蓝色',
},
]
date.forEach(ele => {Factory(ele.content, ele.color)
});
从上面可以看如果想添加其他类时,只需要只需要在这个工厂类中的原型里面添加即可,不比担心创建时做任何修改。工厂方法模式适用于创建多类对象。
抽象工厂模式
通过对类的工厂抽象使其业务用于对产品类簇的创建,而不负责创建某一类穿品的实例
抽象类时一种生命但不能使用的类,当你使用时就会报错,我们可以在类的方法中手动地抛出错误来模拟抽象类。
let Car = function(){}
Car.prototype = {getPrice: function(){return new Error('抽象方法不能调用')
},
getSpeed: function(){return new Error('抽象方法不能调用')
}
}
我们可以看到我们创建的这个 Car 类什么都不能做,没有属性,原型上的方法也不能用,但是这在继承中是很有用的,如果在子类中没有重写这个方法就会报错。
抽象类中定义的方法只是显性地定义一些功能,但没有具体实现,而一个对象是要具备一套完整的功能的,所以用抽象类创建的对象也就抽象了。
// 抽象工厂方法
let VehicleFactory = function(subType, superType){
// 判断抽象工厂中是否有该抽象类
if(typeof VehicleFactory[superType] === 'function'){
// 缓存类
function F()
// 继承父类属性和方法
F.prototype = new VehicleFactory[superType]()
// 将子类 constructor 指向子类
subType.constructor = subType
// 子类原型继承父类
subType.prototype = new F()}else{
// 不存在该抽象类抛出错误
throw new Error('未创建该抽象类')
}
}
// 小汽车抽象类
VehicleFactory.Car = function(){this.type = 'car'}
VehicleFactory.Car.prototype = {getPrice: function(){return new Error('抽象方法不能调用')
},
getSpeed: function(){return new Error('抽象方法不能调用')
},
}
// 公交车抽象类
VehicleFactory.Bus = function(){this.type = 'bus'}
VehicleFactory.Bus.prototype = {getPrice: function(){return new Error('抽象方法不能调用')
},
getSpeed: function(){return new Error('抽象方法不能调用')
},
}
// 货车抽象类
VehicleFactory.Truck = function(){this.type = 'truck'}
VehicleFactory.Truck.prototype = {getPrice: function(){return new Error('抽象方法不能调用')
},
getSpeed: function(){return new Error('抽象方法不能调用')
},
}
可以看到,抽象工厂是一个实现子类继承父的方法,在这个方法中我们需要通过传递子类以及要继承父类的名称,并在抽象工厂中又增加了一次对抽象类存在性的一次判断,如果存在,则将子类继承父类的方法,然后子类通过继承式继承。
抽象工厂是用来创建子类的,所以我们需要一些产品子类,然后让子类继承相应的产品簇抽象类
// 宝马汽车子类
let BMW = function(price, speed){
this.price = price
this.speed = speed
}
// 抽象工厂实现对 Car 抽象类的继承
VehicleFactory(BMW, 'Car')
BMW.prototype.getPrice = function(){return this.price}
BMW.prototype.getSpeed = function(){return this.speed}
// 奔驰汽车子类
let BenzTruck = function(price, speed){
this.price = price
this.speed = speed
}
// 抽象工厂实现对 Car 抽象类的继承
VehicleFactory(BenzTruck, 'Truck')
BenzTruck.prototype.getPrice = function(){return this.price}
BenzTruck.prototype.getSpeed = function(){return this.speed}
let truck = new BenzTruck(100,200)
console.log(truck.getPrice)
console.log(truck.type)
抽象工厂模式是设计模式中最抽象的一种,也是创建模式中唯一一种抽象化创建模式。该模式创建结果不是一个真实对象实例,而是一个类簇,它定制了类的结构,这也就区别于简单工厂模式创建单一对象,工厂方法模式创建多类对象。
建造者模式
将一个复杂对象的构建层与其表示层相互分离,同样的构建过程可采用不同的表示。
工厂模式主要是为了创建对象实例或者类簇,关心的是最终的产出物,不关心过程。而建造者模式在创建对象时要更为复杂,虽然目的也是创建对象,但它更关心的是过程。看下面的例子:
// 创建一位人类
let Human = function(param){
this.skill = param && param.skill || '保密'
this.hobby = param && param.hobby || '保密'
}
Human.prototype = {getSkill: function(){return this.skill},
getHobby: function(){return this.hobby}
}
// 实例化姓名类
let Named = function(name){
let that = this
// (function(name, that){
that.wholeName = name
if(name.indexOf(' ') > -1){that.firstName = name.slice(0,name.indexOf(' '))
that.secondName = name.slice(name.indexOf(' '))
}
// })(name, that)
}
// 实例化职业类
let Work = function(work){
let that = this
// (function(work,that){switch(work){
case 'code':
that.work = '工程师'
that.workDescript = '每天沉迷于编程'
break
case 'UE':
that.work = '设计师'
that.workDescript = '设计也是一种快乐'
break
case 'teacher':
that.work = '教师'
that.workDescript = '分享是一种快乐'
break
default :
that.work = work
that.workDescript = '暂无相关描述'
}
// })(work, that)
}
Work.prototype = {
// 更换期望职位
changeWork: function(work){this.work = work},
// 添加职位描述
changeDescript :function(desc){this.workDescript = desc}
}
我们创建类 3 个类 – 应聘者类、姓名解析类与期望职位类,姓名解析类与期望职位类应该是属于应聘者的属性,接下来我们就使用上面 3 个类来创建一个完整的对象。
let Person = function(name, work){var _person = new Human()
_person.name = new Named(name)
_person.work = new Work(work)
return _person
}
let person = new Person('小 明', 'code')
console.log(person.skill) //'保密'
console.log(person.hobby) //'保密'
console.log(person.name.firstName) //'小'
console.log(person.name.secondName) //'明'
console.log(person.work.work) //'工程师'
console.log(person.work.workDescript) //'每天沉迷于编程'
person.work.changeDescript('修改描述信息')
console.log(person.work.workDescript) //'修改描述信息'
首先创建一位应聘者缓存对象,缓存对象需要属性和方法,然后向缓存对象中添加姓名和一个期望职位,最终就创建出一了位完整的应聘者。
原型模式
用原型实例指向创建对象的类,使用于创建新的对象的类共享原型对象的属性以及方法。
原型模式就是将可复用、可共享、耗时大的从基类中提取出来然后放到其原型中。与前面所讲过的原型继承相同,然后子类通过组合继承或寄生式继承将方法和属性继承下来。对于子类中需要重写的方法进行重写,这样子类创建对象既具有子类的属性和方法也共享了基类的原型方法。
/* 原型模式 */
let LoopImages = function(imgArr, container){
this.imgArr = imgArr
this.container = container
}
LoopImages.prototype = {createImage: function(){console.log('createImage function')
},
changeImage: function(){console.log('changeImage function')
},
getConrainer: function(){console.log(this.container)
}
}
// 上下滑动切换类
let SlideLoopImage = function(imgArr, container){LoopImages.call(this, imgArr, container)
}
SlideLoopImage.prototype = new LoopImages()
// 重写切换方法
SlideLoopImage.prototype.changeImage = function(){console.log('SlideLoopImage changeImage function')
}
let FadeLoopImage = function(imgArr, container){LoopImages.call(this, imgArr, container)
}
FadeLoopImage.prototype = new LoopImages()
// 重写切换方法
FadeLoopImage.prototype.changeImage = function(){console.log('FadeLoopImage changeImage function')
}
// 新增方法
FadeLoopImage.prototype.getImgLength = function(){console.log(this.imgArr.length)
}
let fadeImg = new FadeLoopImage(['a', 'b', 'c', 'd'],'fade')
fadeImg.createImage() //createImage function
fadeImg.changeImage() //FadeLoopImage changeImage function
fadeImg.getImgLength() //4
fadeImg.getConrainer() //fade
不过原型模式更多是用于对对象的创建上。如果创建一个实例对象的构造函数比较复杂或耗时比较长或通过创建多个对象来实现。此时最好不要用 new 关键字去复制这些基类,但可以通过对这些对象属性或者方法进行复制来实现创建,这是原型最初的思想。
let prototypeExtend = function(){var F = function(){}
args = arguments,
len = arguments.length,
i = 0
for (; i < len; i++) {for (const j in args[i]) {F.prototype[j] = args[i][j]
}
}
// 返回缓存类的实例
return new F()}
let penguin = prototypeExtend({
speed: 20,
swim: function(){console.log('游泳速度' + this.speed)
}
},{run: function(){console.log('奔跑速度' + this.speed)
}
},{jump: function(){console.log('跳跃')
}
}
)
penguin.swim() //'游泳速度 20'
penguin.run() //'奔跑速度 20'
penguin.jump() //'跳跃'
单例模式
又称为单体模式,是只允许实例化一次的对象类,可用一个对象类划分一个命名空间,有条例的管理对象上的属性和方法。
单例模式就是为众多的方法划分一个命名空间,就比如 jQuery 一样,jQuery 定义的 animate 方法我们就要用 jQuery.animate() 来访问,这样更容易理解与管理。实现的方法很容易,只需要把这些方法放到一个对象中即可。
var dome = {
a: function(){},
b: function(){}
}
dome.a()
dome.b()
这样的结构很清晰,但是有的时候我们需要静态变量,只能够使用而不能修改,如果定义在全局中的话自然是很不安全的,这时我们就可以把这个静态变量定义到我们的容器中,这个容器只提供取值的方法不提供赋值的方法,这样就可以做到保护变量啦。
let Conf = (function(){
// 私有变量
let conf = {
MAX_NUM: 100,
MIN_NUM: 1,
COUNT: 1000
}
return {get: function(name){return conf[name] ? conf[name] : null
}
}
})()
let count = Conf.get('COUNT')
有时候对于单例对象需要延迟创建,所以在单例中还存在一种延迟创建的形式,也称之为“惰性创建”
// 延迟创建(惰性创建)let LazySingle = (function(){
// 单例实例引用
let _instance = null
// 单例
function Single() {
/* 定义私有属性和方法 */
return {publicMethod: function(){},
publicProperty: '1.0'
}
}
// 获取单例对象接口
return function(){
// 如果为创建单列将创建单例
if(!_instance){_instance = Single()
}
// 返回单例
return _instance
}
})()
单例模式是一个只允许实例化一次的对象类,这么做也是为了节省系统资源,经常作为命名空间对象来实现,通过单例模式可以将各个模块的代码井井有条地梳理在一起。