一个类或对象中每每会包含别的对象。在建立这种成员对象时,你可能习惯于使用常规方式,也即用new关键字和类构造函数。问题在于这回致使相关的两个类之间产生依赖性。javascript
工厂模式用于消除这两个类之间的依赖性,它使用一个方法来决定究竟要实例化哪一个具体的类。这里介绍简单工厂模式(动态选择并生成实例)及真正的工厂模式(亦称工厂方法),后者使一个类的实例化延迟到了子类。而子类能够重写接口方法以便建立的时候指定本身的对象类型。html
1、简单工厂java
拿生产自行车举例子,先看它的类及对应的方法——设计模式
var Bicycle = function() {}; Bicycle.prototype = { // 组装 assemble: function() { }, // 清洗 wash: function() { }, // 骑车 ride: function() { }, // 维修 repair: function() { } };
假设你想开几个自行车商店,每一个店都有集中型号的自行车出售。这能够用一个类来表示——ide
var BicycleShop = function() {}; BicycleShop.prototype = { // 卖自行车 sellBicycle: function(model) { var bicycle; // 注:默认这里的每一款自行车都继承Bicycle,且都有本身的方法实现 switch(model) { case 'The Speedster': bicycle = new Speedster(); break; case 'The Lowrider': bicycle = new Lowrider(); break; case 'The Comfort Cruiser': default: bicycle = new Cruiser(); } // 对自行车进行组装 bicycle.assemble(); // 对自行车进行清洗 bicycle.wash(); // 将自行车交付于客户 return bicycle; } }; // 那么,要出售某种型号的自行车,只要调用sellBicycle方法便可 var californiaCruisers = new BicycleShop(); // 购买你所须要的自行车 var youNeedBicycle = californiaCruisers.sellBicycle('The Speedster');
在状况发生以前,这倒也挺管用。但要是你想在供货目录中加入一款新车型又会怎么样呢?你得为此修改BicycleShop的代码,哪怕这个类的实际功能实际上并无发生改变——依旧是建立一个自行车的新实例,组装它、清洗它,而后把它交给客户。更好的办法是把sellBicycle方法中“建立新实例”这部分工做转交给一个简单工厂对象。模块化
var BicycleFactory = { createBicycle: function(model) { var bicycle; switch(model) { case 'The Speedster': bicycle = new Speedster(); break; case 'The Lowrider': bicycle = new Lowrider(); break; case 'The Comfort Cruiser': default: bicycle = new Cruiser(); } return bicycle; } }; // 这里的BicycleFactory是一个单体,用来把createBicycle方法封装在一个命名空间中。 // 这个方法返回一个实现了Bicycle接口的对象,而后你能够照常对其进行组装和清洗。 var BicycleShop = function() {}; BicycleShop.prototype = { // 卖自行车 sellBicycle: function(model) { var bicycle; // 应用工厂模式建立所须要的自行车 bicycle = BicycleFactory.createBicycle(model); // 对自行车进行组装 bicycle.assemble(); // 对自行车进行清洗 bicycle.wash(); // 将自行车交付于客户 return bicycle; } };
这样,这个BicycleFactory对象能够供各类类来建立新的自行车实例。有关可供车型的全部信息都集中在一个地方管理,因此添加更多车型很容易。从而售车商店BicycleShop与添加具体的自行车车型达到解耦。函数
以上就是简单工厂的一个很好的例子。这种模式把成员对象的建立工做转交给一个外部对象。这个外部对象能够像本例中同样是一个简单的命名空间,也能够是一个类的实例。若是负责建立吃力的方法的逻辑不会发生变化,那么通常说来用单体或静态类方法建立这些成员实例是合乎情理的。但若是你要提供几种不一样品牌的自行车,那么更恰当的作法是把这个建立方法实如今一个类中,并从该类派生出一些子类。ui
2、工厂模式(工厂方法)this
真正的工厂模式与简单工厂模式的区别在于,它不是另外使用一个类或对象来建立自行车,而是使用一个子类。按照正式定义,工厂是一个将其成员对象的实例化推迟到子类中进行的类。spa
// BicycleShop class (abstract) // 该类不能被实例化,只能用来派生子类。 // 设计一个经销特定自行车生产厂家产品的子类须要扩展BicycleShop,从新定义其中的createBicycle方法 var BicycleShop = function() {}; BicycleShop.prototype = { // 卖自行车 sellBicycle: function(model) { var bicycle = this.createBicycle(model); bicycle.assemble(); bicycle.wash(); return bicycle; }, // 建立自行车 createBicycle: function(model) { throw new Error('Unsupported operation on an abstract class.'); } };
如今咱们来定义两个商店,一个商店从Acme公司进货,另外一个则从GeneralProducts公司进货。也就是继承BicycleShop的具体子类
// 从Acme公司进货的商店 var AcmeBicycleShop = function() {}; AcmeBicycleShop.prototype = new BicycleShop(); AcmeBicycleShop.prototype.createBicycle = function(model) { var bicycle; switch(model) { case 'The Speedster': bicycle = new AcmeSpeedster(); break; case 'The Lowrider': bicycle = new AcmeLowrider(); break; case 'The Comfort Cruiser': default: bicycle = new AcmeCruiser(); } return bicycle; };
// 从GeneralProducts公司进货的商店 var GeneralProductsBicycleShop = function() {}; GeneralProductsBicycleShop.prototype = new BicycleShop(); GeneralProductsBicycleShop.prototype.createBicycle = function(model) { var bicycle; switch(model) { case 'The Speedster': bicycle = new GeneralProductsSpeedster(); break; case 'The Lowrider': bicycle = new GeneralProductsLowrider(); break; case 'The Comfort Cruiser': default: bicycle = new GeneralProductsCruiser(); } return bicycle; };
选择同一个自行车款式,不一样品牌的车子——
var AcmeShop = new AcmeBicycleShop(); AcmeShop.sellBicycle('The Lowrider'); var GeneralProductsShop = new GeneralProductsBicycleShop(); GeneralProductsShop.sellBicycle('The Lowrider');
由于两个生产厂家生产的自行车款式彻底相同,因此顾客买车时能够不用关心车到底是哪家生产的。要是他们只想要Acme生产的自行车,他们能够去Acme专卖店买。
增长对其余生产厂家的支持很简单,只要再建立一个BicycleShop的子类并重定义其createBicycle工厂方法便可。咱们也能够对各个子类进行修改,以支持相关厂家其余型号的产品。这是工厂模式最重要的特色。对Bicycle进行通常性操做的代码能够所有写在父类BicycleShop中,而对具体的Bicycle对象进行实例化的工做则被留到子类中。
3、使用原则
1. 动态实现
建立一些用不一样方式实现同一接口的对象,那么可使用一个工厂方法或简单工厂对象来简化选择实现的过程。你一般要与一系列实现了同一个接口、能够被同等对待的类打交道。这是JavaScript中使用工厂模式的最多见的缘由。
2. 节省设置开销
若是对象须要进行复杂而且彼此相关的设置,那么使用工厂模式能够减小每种对象所需的代码量。 若是这种设置只须要为特定类型的全部实例执行一次便可,这种做用尤为突出。把这种设置代码放到类的构造函数中并非一个高效的作法,这是由于即使设置工做已经完成,每次建立新实例的时候这些代码仍是会执行,并且这样作会把设置代码分散到不一样的类中。工厂方法很是适合于这种场合。它能够在实例化全部须要的对象以前先一次性地进行设置。不管有多少不一样的类会被实例化,种方法均可以让设置代码集中在一个地方。 若是所用的类要求加载外部库的话,这尤为有用。工厂方法能够对这些库进行检查并动态加载那些未找到的库。这些设置代码只存在于一个地方,所以之后改起来也方便得多。
4、优点及劣势
优点——
工厂模式的主要好处在于消除对象间的耦合。经过使用工厂方法而不是new关键字及具体类,你能够把全部实例化代码集中在一个位置。这能够大大简化更换所用的类或在运行期间动态选择所用的类的工做。在派生子类时它提供了更大的灵活性。使用工厂模式,你能够先建立一个抽象的父类,而后在子类中建立工厂方法,从而把成员对象的实例化推迟到更专门化的子类中进行。
全部这些好处都与面向对象设计的这两条原则有关:弱化对象间的耦合;防止代码的重复。在一个方法中进行类的实例化,能够消除重复性的代码。这是在用一个对接口的调用取代一个具体的实现。这些都有助于建立模块化的代码。
劣势——
可能有人禁不住想把工厂方法当作万金油去用,把普通的构造函数仍在一块儿。这并不值得提倡。若是根本不可能另外换用一个类,或者不须要在运行期间在一系列可互换的类中进行选择,那就不该该使用工厂方法。大多数类最好使用new关键字和构造函数公开地进行实例化。这可让代码更简单已读。你能够一眼就看到调用的是什么构造函数,而没必要去查看某个工厂方法以便知道实例化的是什么类。工厂方法在其适用场合很是有用,但切勿滥用。若是拿不定主意,那就不要用,由于之后在重构代码时还有机会使用工厂模式。
源自:JavaScript设计模式(人民邮电出版社)——第七章,工厂模式