工厂模式是用来建立对象的一种最经常使用的设计模式。咱们不须要暴露建立对象的具体逻辑,而是将逻辑都封装在一个函数中,那么这个函数将视为一个工厂。工厂模式可分为
简单工厂
、工厂方法
和抽象工厂
。css
简单工厂模式
又称为 静态工厂方法
或 工厂函数
,是由一个工厂对象(函数)用来建立一组类似的产品对象实例。面试
把须要的方法都封装在一个函数内,经过调用该函数返回我对应的对象。设计模式
实例化对象建立的 例如体育商品店购买体育器材,当咱们购买一个篮球和及其介绍时,只需告诉售货员,售货员就会帮咱们找到须要的东西。ide
function Bask(){
this.name = "篮球"
}
Bask.prototype.getBask = function(){
console.log("篮球队员须要5个");
}
function Football(){
this.name = "足球";
}
Football.prototype.getFootball = function(){
console.log("足球队员须要11个");
}
function Sort(type){
switch(type){
case "bask":
return new Bask();
break;
case "foot":
return new Football();
break;
default :
console.log("只有篮球和足球");
}
}
let f = Sort("bask");
f.name; // "篮球"
f.getBask() // 篮球队员须要5个
复制代码
Sort()
就是这样一个工厂函数,给 Sort()
传入对应的参数就能够获得咱们须要的对象了。
一个对象有时也能够代替许多类
函数
简单工厂模式 中将相似的功能提取出来,不类似的针对处理,这点很像咱们的继承的 寄生模式
,但这里没有父类,因此不须要继承。只须要建立一个对象,而后经过对这个对象大量扩展属性和方法,并在最终的时候返回来。oop
"好比,想建立一些书,那么书都是有一些类似的地方发,好比目录、页码等。不类似的地方,好比书名、出版时间,书的类型等。对于建立的对象类似属性好处理,对于不一样的属性就要针对性的处理了。好比咱们将不一样的属性做为参数传递进来处理。"post
经过建立一个新对象而后加强其属性和功能实现性能
function createBook(name,time,type){
let obj = {};
obj.name = name;
obj.time = time;
if(type == "js"){
obj.type = "js 书籍";
}else if (type == "css"){
obj.type = "css 书籍"
}else {
obj.type = type;
}
obj.getName = function(){
console.log(obj.name);
}
obj.getType = function(){
console.log(obj.type)
}
return obj;
}
let c = createBook("JavaScript 高级程序第三版","2019","js");
c.getType(); // js 书籍
复制代码
实例化建立的和自建立对象的区别 经过类实例化建立的对象,若是这些类都继承同一个父类,那么他们父类的原型的方法时能够共用的。自建立方式建立的对象都是一个新的个体,所以他们的方法是不能共用的。ui
对于一些重复性的需求咱们均可以采用工厂模式来建立一些对象,可让这些对象共用一些资源也有私有的资源。不过对于简单工厂模式,它的使用场合一般也就是限制在建立单一的对象。this
在简单工厂模式中每次添加构造函数方法的时候都须要修改 2 个地方 ( 添加构造函数类和修改工厂函数 ),如今改用 工厂方法模式 ,把工厂方法看做是一个实例化对象的工厂,只负责把要添加的类添加到这个工厂就能够了。
以上面的 书籍 代码为例
function CreateBook(type,name,time){
if(this instanceof CreateBook){
return new this[type](name,time)
}else{
return new CreateBook(type,name,time)
}
}
CreateBook.prototype = {
constructor: CreateBook,
getName:function(name,time){
console.log(name,time);
},
getType: function(name,time){
console.log(name,time);
}
}
复制代码
直接把要添加的类写在 CreateBook 这个工厂类的原型里面就能够避免修改不少操做了。
对于建立不少类对象,简单工厂模式就不适合用了,这是简单工厂模式的应用局限,当前这也正是 工厂方法 的价值所在,经过 工厂方法模式 能够轻松建立多个类的实例对象,这样工厂方法在建立对象的方式也避免了使用者和对象类之间的耦合度,用户不须要关心建立该对象的具体类,只须要调用工厂方法便可。
在了解抽象工厂模式以前,咱们先了解一下什么是抽象类。
** 抽象类** 在 js 中还保留着一个 abstract
保留字,目前来讲还不能像传统面向对象那样去建立一个抽象类。抽象类是一种声明但不能使用的类,当使用就会报错。在 js 中,咱们能够用类来模拟一个抽象类,而且在类的方法中手动的抛出错误。
function Car(){};
Car.prototype = {
constructor : Car,
getPrice:function(){
return new Error("抽象类的方法不能被调用!");
}
getSeed:function(){
return new Error("抽象类的方法不能被调用!");
}
}
复制代码
Car 类其实什么也没作,建立的时候没有任何属性,原型中的方法也不能使用。可是在继承上颇有用,由于定义了一种类,而且定义了该类所必备的方法,若是子类中没有重写这些方法就会报错,这点很重要,由于在一些大型的应用中,总有一些子类继承另外一些父类,这些父类常常会定义一些必要的方法,却没有具体实现,好比 Car 类同样,继承的子类若是没有本身定义所必备的方法就会调用父类的,这时若是父类可以提供一个友好的提示,那么对于忘记重写子类方法的这些错误遗漏是颇有必要的, 这也是抽象类的一个做用,定义一个产品簇,并声明一些所必备的方法,若是子类没有声明一些必备的方法重写就会报错。
在 ES6 中也没有实现abstract
, 但要比咱们上面那种写法简约不少。ES6 中采用 new.target
来模拟出抽象类,new.target
指向直接被 new
执行的函数,咱们对 new.target
进行判断,若是指向了该类则抛出错误表示这是一个抽象类。
class User{
constructor(){
if(new.target === User){
console.log("抽象类不能被实例化");
}
}
}
class U extends User{
constructor(name,age){
super();
}
getname(){
console.log(this.name)
}
}
let a = new User(); // 抽象类不能被实例化
let a1 = new U("kk",2);
a1.name // "kk"
复制代码
因此,通常用它做为父类来建立一些子类。
function VehicleFactory(subType, superType) {
// VehicleFactory[superType] 对象获取属性
if (typeof VehicleFactory[superType] === "function") {
function F() { };
F.prototype = new VehicleFactory[superType]();
subType.prototype = new F();
subType.constructor = subType;
} else {
throw new Error("未建立该抽象类")
}
}
VehicleFactory.Car = function () {
this.type = "car";
}
VehicleFactory.Car.prototype = {
getPrice: function () {
return new Error("抽象方法不能使用!")
}
}
//汽车子类
function BMW(price) {
this.price = price;
}
VehicleFactory(BMW, 'Car');
BMW.prototype = {
getPrice: function () {
console.log(this.price);
}
}
let b = new BMW("小汽车滴滴滴...");
b.getPrice(); //小汽车滴滴滴...
复制代码
抽象工厂 VehicleFactory 实际上是一个实现子类继承父类(建立子类)的方法,在该方法中须要经过传递 子类和要继承的父类(抽象类) 的名称,而且在抽象工厂方法中增长了对抽象类是否存在的判断,存在则将子类继承父类,继承父类的过程当中须要对 过渡类的原型继承时,咱们不是继承父类的原型,而是经过 new
关键字复制父类的一个实例,这是由于 过渡类不该该仅仅是继承父类的原型方法,还要继承父类的对象属性
,因此要经过 new
关键字将父类的构造函数执行一遍来复制构造函数中的属性和方法。
对抽象工厂添加抽象类也很特殊,由于抽象工厂是个方法不须要实例化,因此只有一份, 所以直接给抽象工厂添加类的属性就能够了。因而咱们就能够经过点(.)语法在抽象工厂上添加了小汽车簇抽象类(Car)。
抽象工厂模式建立出的不是一个真实的对象实例,而是一个类簇,制定了类的结构,这也就是区别简单工厂模式建立一个单一对象,工厂方法模式建立多类对象。
工厂模式 主要就是为建立实例对象或类簇(抽象工厂),不在意过程是啥,只关心最后返回的对象。
建立者模式概念
在建立对象时比较复杂,但更关心的是建立对象的过程,根据需求分解成多个对象,最后在拼接到一个对象返回。
好比下面是一个专门负责招聘的需求功能
// 建立一个 人类方法
function Human(param) {
this.skill = param && param.skill || "保密";
this.hobby = param && param.hobby || "保密";
}
// 提供原型方法
Human.prototype = {
getSkill: function () {
return this.skill;
},
getHobby: function () {
return this.hobby;
}
}
// 建立名字
function Named(name) {
this.name = name;
}
// 建立工做职位
function Work(work) {
let that = this;
switch (work) {
case 'Java':
that.work = "Java工程师";
that.workTxt = "天天沉迷 Java 不可自拔!";
break;
case 'JavaScript':
that.work = "JS 工程师";
that.workTxt = "天天沉迷 JS 不可自拔!";
break;
case 'UI':
that.work = "UI 设计师";
that.workTxt = "设计是一种艺术!";
break;
case 'PM':
that.work = "产品经理";
that.workTxt = "天天都在作需求!";
break;
default:
that.work = work;
that.workTxt = "对不起,不清楚你的分类";
}
}
// 修改工做职位
Work.prototype.changeWork = function(work){
this.work = work;
}
// 修改工做描述
Work.prototype.changeWorkTxt = function(txt){
this.workTxt = txt;
}
// 重点,建立一个面试者,在这个阶段进行拼接
function Person (name,work){
let p = new Human();
p.w = new Work(work);
p.name = new Named(name);
return p;
}
let p1 = newPerson("xiaoming", "Java");
console.log(p1.w.work); // "Java工程师"
console.log(p1.name.name);// "xiaoming"
p1.w.changeWork("UI");
console.log(p1.w.work);// "UI"
console.log(p1.getSkill()); //保密
复制代码
Person 就是一个建立者函数,在该函数内咱们把 3 个类组合调用,就能够建立出一个完整的应聘者对象了。
建造者模式中,咱们更关心建立对象的过程。把功能拆分在拼接获得一个完整去对象。主要针对复杂业务的解耦。
原型模式 将原型对象指向建立对象的类,使这些类共享原型对象的属性和方法。
这是基于 JS 原型链实现对象之间的继承,这种继承是一种属性或方法的共享,而不是对属性和方法的复制。
在建立的类中,存在基类,其定义的属性和方法能被子类继承。
原型模式将可复用的、可共享的、耗时较长的从基类中提出来放在基类的原型中,而后子类经过组合继承或寄生组合式继承把属性和方法继承下来,对于子类中那些须要重写的方法进行重写,这样子类建立的对象既具备子类的属性和方法同时也共享了基类的原型方法。
例子
好比页面中常常见到的焦点图,焦点图的切换效果都是多变的,有左右切换的,有上下切换的还有渐隐切换的等等。所以咱们应该抽象出一个基类,根据不一样需求来重写继承的属性和方法。
代码
function LoopImage(imgArr,container){
this.imgArr = imgArr;
this.container = container;
this.createImg = function(){} // 建立轮播图片
this.changeImg = function(){} // 切换下一张图片
}
// 上下切换效果
function SlideLoopImg(imgArr,container){
LoopImage.call(this,imgArr,container);
this.changeImg = function(){
console.log("SlideLoopImg changeImg");
}
}
// 渐隐切换效果
function FadeLoopImg(imgArr,container,arrow){
LoopImage.call(this,imgArr,container);
// 切换箭头私有变量
this.arrow = arrow;
this.changeImg = function(){
console.log("FadeLoopImg changeImg");
}
}
// 实例化一个渐隐切换效果图片类
let fadeImg = new FadeLoopImg(["01.jpg","02.jpg"],"slide",["left.jpg","right.jpg"]);
fadeImg.changeImg(); // FadeLoopImg changeImg
复制代码
如上代码还存在一些问题,首先看咱们的基类 LoopImage,做为基类是要被子类继承的,那么此时将属性和方法都写在基类的构造函数里就会有一些问题,好比每次子类继承父类都要从新建立一次,又或者父类中的构造函数中存在不少耗时长的逻辑,亦或者每次初始化都是一些重复性的东西,对性能的消耗不少。
因此咱们须要一种共享机制,这样每当建立基类的时候,对于一些简单或者差别化的东西放在构造函数内,对于可重复,耗时长的逻辑放在基类的原型中。这样就能够避免损耗性能。
function LoopImage(imgArr,container){
this.imgArr = imgArr;
this.container = container;
}
LoopImage.prototype.createImg = function(){} // 建立轮播图片
LoopImage.prototype.changeImg = function(){} // 切换下一张图片
// 上下切换效果
function SlideLoopImg(imgArr,container){
LoopImage.call(this,imgArr,container);
}
SlideLoopImg.prototype = new LoopImage();
SlideLoopImg.prototype.changeImg = function(){
console.log("SlideLoopImg changeImg");
}
// 渐隐切换效果
function FadeLoopImg(imgArr,container,arrow){
LoopImage.call(this,imgArr,container);
// 切换箭头私有变量
this.arrow = arrow;
}
FadeLoopImg.prototype = new LoopImage();
FadeLoopImg.prototype.changeImg = function(){
console.log("FadeLoopImg changeImg");
}
// 实例化一个渐隐切换效果图片类
let fadeImg = new FadeLoopImg(["01.jpg","02.jpg"],"slide",["left.jpg","right.jpg"]);
fadeImg.changeImg(); // FadeLoopImg changeImg
复制代码
原型对象是一个共享的对象,不论是父类的实例对象仍是子类的继承,都是靠一个指针引用的。因此在任什么时候候对基类或者子类的原型进行拓展,全部实例化的对象或者类都能获取到这些方法。
又称为单体模式,只容许实例化一次的对象类,有时候咱们也用一个对象来规划一个命名空间,以便管理对象上的属性和方法。
命名空间 也有人称为名称空间,用来约束每一个人定义的变量以免全部不一样的人定义的变量存在重复致使冲突的。
单例模式例子
var Ming = {
g:function(id){
return document.getElementById(id);
},
c:function(id,key,value){
this.g(id).style[key] = value;
}
}
复制代码
在单例模式中想要使用定义的方法必定要加上命名空间 Ming,因此在上面代码中的 c 方法中的 g 函数调用的时候要改为 Ming.g。因为 g 方法和 c 方法都在 Ming 中,也就是说这 2 个方法都是单例对象 Ming 的。而对象中的 this 指向当前对象。因此咱们也能够像上面代码那样直接使用 this.g 。
单例模式 除了定义命名空间的做用以外还有一个做用就是经过单例模式来管理代码库中的各个模块。
好比咱们之后在写本身的小型方法库时能够用单例模式来规范本身代码库的各个模块。
var A={
Util:{
Util_1:function(){},
Util_2:function(){}
},
Tool:{
Tool_1:function(){},
Tool_2:function(){}
},
Ajax:{
getName:function(){},
postName:function(){}
}
// ...
}
复制代码
使用模块方法时
A.Util.Util_1();
A.Ajax.getName();
// ....
复制代码
惰性单例
惰性单例指在须要的时候才会建立,也称为延迟建立。 惰性单例模式,用到时才建立,再次使用是不须要在建立的。
var Lazy = (function(){
var instance = null;
// 单例
function Sligle(){
/*这里能够定义私有属性和方法*/
return {
p:function(){},
pv:"1.0"
}
}
// 获取单例对象接口
return (function(){
// 若是尚未建立单例才开始建立
if(!instance ){
instance = Sligle();
}
// 返回单例
return instance;
})
})()
Lazy().p // 经过 Lazy对象 获取内部建立的单例模式对象
复制代码
参考:JavaScript 设计模式