既然标题中提到了创建型设计模式,那就先来解释一下什么叫创建型设计模式?创建型设计模式是一类处理对象创建的设计模式,通过某种方式控制对象的创建来避免基本对象创建时可能导致设计上的问题或增加设计上的复杂度。--引自张荣铭的【JavaScript设计模式】
在一篇博客中,博主为了写工厂模式,在文章开始进行了一个简答的问题陈述,下面我将这段话也写进来:
原文“想象一个场景:如果你要求去买一些东西:板烧鸡腿汉堡,可乐和薯条,那么人们会非常自然的跑去麦当劳去购买对吧。为什么我们会想到去麦当劳呢?因为这些东西都是一类食物,然后麦当劳作为一个'工厂',可以一条龙的提供给消费者,如果没有麦当劳,那么我们需要分别去可乐,薯条和板烧鸡腿汉堡的店面去分别买这些食物,那么购买效率会很低。所以可以说麦当劳就是一个销售食物的工厂模式。”
对于工厂模式也会有三种类型的实现方式,分别是:简单工厂模式,工厂方法模式和抽象工厂模式。它们分别是在各自基础上有一定的改进。我们看一下这三种模式的说明:
1、简单工厂模式
简单工厂模式是由一个方法来决定到底要创建哪个类的实例,而这些实例经常都拥有相同的接口。这种模式主要用在所实例化的类型在编译期并不能确定, 而是在执行期决定的情况。 因为它仅仅只是分离了对象的创建和对象的使用,一旦有新的产品加入,就必须修改简单工厂中对象创建的方法,这样并没有遵守对修改封闭,对扩展开放的原则。所以这个模式的抽象和分离的还不够彻底。说的通俗点,就像公司茶水间的饮料机,要咖啡还是牛奶取决于你按哪个按钮。
举个例子,比如说 有一家体育用品商店,里面有卖很多体育器材,当我们要去体育用品店购买一个篮球时,此时我们只需要问售货员,那么他就会帮我们找到我们要的篮球。那如果下次我们又要买羽毛球呢,也同样只需要告诉售货员我们要买羽毛球,那要如何实现呢?
var Basketball = function(){
this.name = 'Basketball';
}
Basketball.prototype = {
getIntro : function (){
console.log("This is Basketball ……");
},
getPrice : function (){
console.log("The price of Basketball is $60");
}
}
var Football = function(){
this.name = 'Football';
}
Football.prototype = {
getIntro : function (){
console.log("This is Football ……");
},
getPrice : function (){
console.log("The price of Football is $60");
}
}
var Tennis = function(){
this.name = 'Tennis';
}
Tennis.prototype = {
getIntro : function (){
console.log("This is Tennis ……");
},
getPrice : function (){
console.log("The price of Tennis is $60");
}
}
var SportsFactory = function(name){
switch(name){
case 'NBA':
return new Basketball();
break;
case 'wordCup':
return new Football();
break;
case 'FrenchOpen':
return new Tennis();
break;
}
}
接下来,我们来测试一下:
var tennis = SportsFactory("FrenchOpen");输出结果:
console.log(tennis);
console.log(tennis.name);
tennis.getPrice();
{ name: 'Tennis' }
Tennis
The price of Tennis is $60
2、工厂方法模式
工厂方法模式是一种实现”工厂”概念的面向对象设计模式。实质是定义一个创建对象的接口,但是让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。创建一个对象常常需要复杂的过程,所以不适合在一个复杂的对象中。创建对象可能会导致大量的重复代码,也可能提供不了足够级别的抽象。工厂方法模式通过定义一个单独的创建对象的方法来解决这些问题,由子类实现这个方法来创建具体类型的对象。
在简单工厂模式的基础上,我们已经解决了入口不统一的问题,但是还有一个问题没有解决:
加入一个新的类需要修改多部分:首先我们需要在SportsFactory工厂内部加入如何实现,然后加到switch部分;所以这是一次修改的需求,我们需要修改多个地方。那么我们看能不能尝试更抽象一点,尽可能减少需要修改的地方;
var SportsFactory = function(type,content){所以这里加入了一个 return this[type](content);的方法自动代替了之前的switch的方法。以后需要加入内容只需要修改 SportsFactory的prototype就可以了。
this.name = content.name; //共同的部分放在这里
return this[type](content);
};
SportsFactory.prototype = {
football:function(content){
console.log("这里加入和football相关的独特内容" +this.name);
},
basketball:function(content) {
console.log("这里加入和basketball相关的独特内容" +this.name);
}
};
var football = new SportsFactory("football",{name:"football"}); //football in the prototype
为了安全起见,我们需要采用安全模式类,其实这个模式是用来解决如果不在构造函数前面加上new的话,会报错的问题。解决的思路其实是把new封装在构造函数之内:
var SportsFactory = function(type,content){总结:我们使用简单工厂模式解决了入口不统一的问题,然后使用工厂模式解决了修改地点不统一的问题。
if(!(this instanceof SportsFactory)){
return new SportsFactory(type,content); //多一行判断即:如果没有带new,我自己帮你new一个返回就好;
}
this.name = content.name;
return this[type](content);
};
SportsFactory.prototype = {
football:function(content){
console.log("这里加入和football相关的独特内容" +this.name);
},
basketball:function(content) {
console.log("这里加入和basketball相关的独特内容" +this.name);
}
};
var football = SportsFactory("football",{name:"football"}); //这里如果掉了new也会正常执行;
3、抽象工厂模式
抽象工厂是工厂模式的升级版,他用来创建一组相关或者相互依赖的对象。上节学习了工厂模式,类的创建依赖工厂类,程序需要扩展时,我们必须创建新的工厂类。工厂类是用来生产产品的,那我们也可以把“工厂类当成我们要生产的产品”,所以抽象工厂就是“工厂的工厂”,即生产工厂的工厂。
var Ball = function(){};
Ball.prototype = {
getPrice:function(){ throw new Error("抽象方法不能调用")},
getSpeed:function(){ throw new Error("抽象方法不能调用")}
};
//这里使用Object.create()继承,子类到父类中会多一个中间过渡函数Temp(){};防止在子类的prototype覆盖父类;可见参考资料
var basketball= Object.create(Ball.prototype);
basketball.getPrice(); // 抽象方法不能调用
basketball.getPrice = function(){
console.log("I am getting price");
};
basketball.getPrice(); //I am getting price
抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联(例如不同厂商生产CPU)。
简单工厂模式解决了入口不统一的问题,工厂模式解决了修改地点不统一的问题,抽象工厂模式解决了子类实现不规范的问题。