一个类或对象中往往会包含别的对象。在创建这种成员对象时,你可能习惯于使用常规方式,也即用new关键字和类构造函数。问题在于这回导致相关的两个类之间产生依赖性。

  工厂模式用于消除这两个类之间的依赖性,它使用一个方法来决定究竟要实例化哪个具体的类。这里介绍简单工厂模式(动态选择并生成实例)及真正的工厂模式(亦称工厂方法),后者使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。

  一、简单工厂

  拿生产自行车举例子,先看它的类及对应的方法——

var Bicycle = function() {};

Bicycle.prototype = {

    // 组装
assemble: function() { }, // 清洗
wash: function() { }, // 骑车
ride: function() { }, // 维修
repair: function() { }
};

  假设你想开几个自行车商店,每个店都有集中型号的自行车出售。这可以用一个类来表示——

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与添加具体的自行车车型达到解耦。

  以上就是简单工厂的一个很好的例子。这种模式把成员对象的创建工作转交给一个外部对象。这个外部对象可以像本例中一样是一个简单的命名空间,也可以是一个类的实例。如果负责创建吃力的方法的逻辑不会发生变化,那么一般说来用单体或静态类方法创建这些成员实例是合乎情理的。但如果你要提供几种不同品牌的自行车,那么更恰当的做法是把这个创建方法实现在一个类中,并从该类派生出一些子类。

  二、工厂模式(工厂方法)

  真正的工厂模式与简单工厂模式的区别在于,它不是另外使用一个类或对象来创建自行车,而是使用一个子类。按照正式定义,工厂是一个将其成员对象的实例化推迟到子类中进行的类。

// 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对象进行实例化的工作则被留到子类中。

  三、使用原则

  1. 动态实现

  创建一些用不同方式实现同一接口的对象,那么可以使用一个工厂方法或简单工厂对象来简化选择实现的过程。你通常要与一系列实现了同一个接口、可以被同等对待的类打交道。这是JavaScript中使用工厂模式的最常见的原因。

  2. 节省设置开销

   如果对象需要进行复杂并且彼此相关的设置,那么使用工厂模式可以减少每种对象所需的代码量。 如果这种设置只需要为特定类型的所有实例执行一次即可,这种作用尤其突出。把这种设置代码放到类的构造函数中并不是一个高效的做法,这是因为即便设置工作已经完成,每次创建新实例的时候这些代码还是会执行,而且这样做会把设置代码分散到不同的类中。工厂方法非常适合于这种场合。它可以在实例化所有需要的对象之前先一次性地进行设置。无论有多少不同的类会被实例化,种方法都可以让设置代码集中在一个地方。 如果所用的类要求加载外部库的话,这尤其有用。工厂方法可以对这些库进行检查并动态加载那些未找到的库。这些设置代码只存在于一个地方,因此以后改起来也方便得多。

  

  四、优势及劣势

  优势——

  工厂模式的主要好处在于消除对象间的耦合。通过使用工厂方法而不是new关键字及具体类,你可以把所有实例化代码集中在一个位置。这可以大大简化更换所用的类或在运行期间动态选择所用的类的工作。在派生子类时它提供了更大的灵活性。使用工厂模式,你可以先创建一个抽象的父类,然后在子类中创建工厂方法,从而把成员对象的实例化推迟到更专门化的子类中进行。

  所有这些好处都与面向对象设计的这两条原则有关:弱化对象间的耦合;防止代码的重复。在一个方法中进行类的实例化,可以消除重复性的代码。这是在用一个对接口的调用取代一个具体的实现。这些都有助于创建模块化的代码。

  劣势——

  可能有人禁不住想把工厂方法当做万金油去用,把普通的构造函数仍在一起。这并不值得提倡。如果根本不可能另外换用一个类,或者不需要在运行期间在一系列可互换的类中进行选择,那就不应该使用工厂方法。大多数类最好使用new关键字和构造函数公开地进行实例化。这可以让代码更简单已读。你可以一眼就看到调用的是什么构造函数,而不必去查看某个工厂方法以便知道实例化的是什么类。工厂方法在其适用场合非常有用,但切勿滥用。如果拿不定主意,那就不要用,因为以后在重构代码时还有机会使用工厂模式。

源自:JavaScript设计模式(人民邮电出版社)——第七章,工厂模式

【读书笔记】读《JavaScript设计模式》之工厂模式的更多相关文章

  1. 读书笔记之 - javascript 设计模式 - 装饰者模式

    本章讨论的是一种为对象增添特性的技术,它并不使用创建新子类这种手段. 装饰者模式可以透明地把对象包装在具有同样接口的另一对象之中,这样一来,你可以给一些方法添加一些行为,然后将方法调用传递给原始对象. ...

  2. 读书笔记之 - javascript 设计模式 - 享元模式

    本章探讨另一种优化模式-享元模式,它最适合于解决因创建大量类似对象而累及性能的问题.这种模式在javascript中尤其有用,因为复杂的javascript代码很快就会用光浏览器的所有可用内存,通过把 ...

  3. 读书笔记之 - javascript 设计模式 - 责任链模式

    责任链模式可以用来消除请求的发送者和接收者之间的耦合.这是通过实现一个由隐式地对请求进行处理的对象组成的链而做到的.链中的每个对象可以处理请求,也可以将其传给下一个对象. 责任链的结构: 责任链由多个 ...

  4. 读书笔记之 - javascript 设计模式 - 工厂模式

    一个类或者对象中,往往会包含别的对象.在创建这种对象的时候,你可能习惯于使用常规方式,即用 new 关键字和类构造函数. 这会导致相关的俩个类之间产生依赖. 工厂模式,就是消除这俩个类之间的依赖性的一 ...

  5. 再起航,我的学习笔记之JavaScript设计模式06(工厂方法模式)

    上一次已经给大家介绍了简单工厂模式,相信大家对创建型设计模式有了初步的了解,本次我将给大家介绍的是工厂方法模式. 工厂方法模式 工厂方法模式(Factory Method):通过对产品类的抽象使其创建 ...

  6. 读书笔记之 - javascript 设计模式 - 门面模式

    门面模式有俩个作用: 简化类的接口 消除类与使用它的客户代码之间的耦合 在javascript中,门面模式常常是开发人员最亲密的朋友.它是几乎所有javascript库的核心原则,门面模式可以使库提供 ...

  7. 再起航,我的学习笔记之JavaScript设计模式08(建造者模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...

  8. JavaScript设计模式之工厂模式

    一.工厂模式概念 工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类.该模式使一个类的实例化延迟到了子类.而子类可以重写接口方法以便创建的时候指定自己的对象类型(抽象工厂). 这个模 ...

  9. 再起航,我的学习笔记之JavaScript设计模式09(原型模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 我们 ...

  10. 再起航,我的学习笔记之JavaScript设计模式11(外观模式)

    经过一段时间的学习与分享,我们对创建型设计模式已经有了一定的认识,未来的一段时间里我们将展开新的篇章,开始迈入结构性设计模式的学习. 结构性设计模式与创建型设计模式不同,结构性设计模式更偏向于关注如何 ...

随机推荐

  1. 数组实现栈的结构(java)

    自定义数组实现栈的结构. package test; public class MyArrayStackClient { public static void main(String[] args) ...

  2. footable动态载入数据

    footable_redraw事件 $('#scan').on('click',function(){ var html = '<tr><td>mayidudu</td& ...

  3. 15个Linux Wget下载实例终极指南

    15个Linux Wget下载实例终极指南 Linux wget是一个下载文件的工具,它用在命令行下.对于Linux用户是必不可少的工具,尤其对于网络管理员,经常要下载一些软件或从远程服务器恢复备份到 ...

  4. JS 瀑布流布局

    瀑布流布局 HTML <!DOCTYPE html> <html> <head> <meta charset="utf-8"> &l ...

  5. ggplot绘图学习笔记

    0.查看R的系统帮助文档 标度 scale breaks, labels, limits, labs dose <- c(20, 30, 40, 45,60) drugA <- c(16, ...

  6. javascript仿天猫加入购物车动画效果

    javascript仿天猫加入购物车动画效果   注意:首先需要声明的是:代码原思路不是我写的,是在网上找的这种效果,自己使用代码封装了下而已:代码中都有注释,我们最主要的是理解抛物线的思路及在工作中 ...

  7. 用纯原生js实现jquery的ready函数(两种实现)

    第一种实现方式: var dom = new function() { var dom = []; dom.isReady = false; dom.isFunction = function(obj ...

  8. Memcached的配置和使用

    1.下载windows版本,64位下载地址: http://s3.amazonaws.com/downloads.northscale.com/memcached-win64-1.4.4-14.zip ...

  9. FineUI第五天---按钮控件

    按钮控件 <x:Button runat="server" ID="按下" Text="按下"></x:Button> ...

  10. Eclipse的模板设置代码

    Eclipse Java注释模板设置详解   设置注释模板的入口: Window->Preference->Java->Code Style->Code Template 然后 ...