1、简介:
一个类或对象往往会包含别的对象。在创建这种对象时,你可能习惯于使用常规方法,即用new关键字和类构造函数。问题在于这会导致两个类之间产生依赖性。比如:现在有几个PC生厂商,生产台式机,每个台式机的组成部分都是CPU、Hardisk、MontherBoard。生产商从各个硬件商收购硬件,并自行组装他们卖给顾客,于是我们如下设计这些类:
function CPU(){ this.frequency = 1.2; this.core = 1; } function Hardisk(){ this.capacity = 320; } function MotherBoard(){} function PC(){} PC.prototype.addCPU = function(cpu){ if(!(cpu instanceof CPU)){ throw new Error('param type error: the param should be instanceof "CPU"'); } this.cpu = cpu; return this; }; PC.prototype.addDisk = function(disk){ if(!(disk instanceof Hardisk)){ throw new Error('param type error: the param should be instanceof "Hardisk"'); } this.hardisk = disk; return this; }; PC.prototype.addMotherBoard = function(mboard){ if(!(mboard instanceof MotherBoard)){ throw new Error('param type error: the param should be instanceof "Hardisk"'); } this.MotherBoard = mboard; return this; }; /*PC生产商*/ function PCShop(){} PCShop.prototype.createPC = function(){ var pc = new PC(); pc.addCPU(new CPU()).addDisk(new Hardisk()).addMotherBoard(new MotherBoard); return pc; };PCShop的createPC函数所做的工作是生产一个PC,接着给他组装上各种硬件,看起来似乎没啥大问题,假如生厂商业务扩大了,开始做笔记本的生意了,而笔记本虽然也包括CPU、Hardisk、MotherBoard,但是这些硬件和台式机的又有区别,适用于笔记本上的硬件继承于原来的硬件,你怎样才能较容易的改变createPC以让它用这些新类型的对象创建PC呢? 这种情况下,改变的最大障碍是createPC对被实例化的类进行了硬编码,也许你可以在函数中增加参数,并增加一些代码组装笔记本。如果某天,厂商又要生产平板电脑了呢? 这种不灵活意味着我们应该重新设计,通过以下工厂模式可以弱化对象之间的耦合。
2、抽象工厂(Abstrack Factory)
抽象工程提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。我们把上述代码中的“创建新实例”这部分工作分离出来,转交给工厂对象,上述情景可以抽象出一个基本的工厂对象,这个基本的工厂对象提供生产产品的接口,更具体的工厂继承这个基本工厂,根据需要实现更具体的接口。一个应用中一般每个产品系列只需一个具体的工厂,因此工厂通常最好实现为一个单例。于是我们首先增加一个基本工厂的单例,在很多情况下,这个基本工厂都实现为抽象类,即只是提供公共接口,不过特定于我们的环境下,实现为提供默认的产品:
*=====修改和增加的代码=====*/ /*PC工厂*/ var PCFactory = { makeCPU:function(){ return new CPU(); }, makeHardisk:function(){ return new Hardisk(); }, makeMotherBoard:function(){ return new MotherBoard(); } }; /*PC生产商*/ function PCShop(){} PCShop.prototype.createPC = function(factory){ var pc = new PC(), cpu = factory.makeCPU(), disk = factory.makeHardisk(), mbo = factory.makeMotherBoard(); pc.addCPU(cpu).addDisk(disk).addMotherBoard(mbo); return pc; };上述代码新增加了一个PCFactroy对象,提供了生产基本产品的接口,在createPC函数中,增加了一个工厂参数,用于获得各种产品,这里并未检查参数的类型,实际应用中,应该配合接口一起工作。我们现在来看看,加入厂商增加笔记本产品的实现,我们说过,一个实例化的工厂对应一个产品系列,于是我们增加一个笔记本工厂,同时根据笔记本硬件和台式机的差距,新增加适合于笔记本的硬件产品类。设计如下:
/*=====修改和增加的代码=====*/ function NoteCPU(){ NoteCPU.superClass(this); this.type = '笔记本CPU'; } NoteCPU.extend(CPU); //用到了以前我们增加的extend函数 /*笔记本工厂单例,内部继承PC工厂,*/ var NoteBookFactory = function(){ var F = function(){}; F.prototype.makeCPU = function(){ //实现自己的mkaeCPU方法,产生笔记本CPU return new NoteCPU(); }; F.extend(PCFactory); var obj = new F(); return obj; }();上述代码新增了一个NoteCPU类,继承于CPU类,这里简单起见,没有增加其他新的硬件类,笔记本工厂单例对象通过自运行函数在内部创建构造函数,实现了单例的继承,当然也可以直接定义NoteBookFactroy为字面量对象,自己扩展一个函数将PCFactory中的未改变的接口赋给NoteBookFactory。我们无需改动createPC,就可以任意的增加新的产品系列,生产台式机这样调用: var shop = new PCShop(); pc.createPC(PCFactroy); 生产笔记本则可以这样调用:pc.createPC(NoteBookFactory);
3、工厂方法(Factroy Method)
工厂方法定义了一个用于创建对象的接口,让子类决定实例化哪一个类,它使一个类的实例化延迟到子类。 从示例角度看, PC厂商增加了笔记本业务,可以看成新开了一家笔记本店,即NoteBookShop, 台式机店面按着自己的流程组装机器(createPC),同样是获得CPU(makeCPU),获得Hardisk(makeHardisk),获得MotherBoard(makeMotherBoard), 笔记本店面的组装流程与台式机是一样的,唯一不同的是获得的硬件类型稍有差异,也许哪天扩展了平板电脑的业务,但流程还是一致的,区别的只是硬件获得的类型,于是我们可以把PCShop作为基类,具有一组基本的制作硬件的接口,而其他类型的PC从这继承而来,实现自己的接口。设计如下:
/*=====修改和增加的代码=====*/ /*PC工厂,这里默认生产基本的台式机*/ var PCShop = { makeCPU:function(){ return new CPU(); }, makeHardisk:function(){ return new Hardisk(); }, makeMotherBoard:function(){ return new MotherBoard(); }, createPC:function(){ var pc = new PC(), cpu = this.makeCPU(), disk = this.makeHardisk(), mbo = this.makeMotherBoard(); pc.addCPU(cpu).addDisk(disk).addMotherBoard(mbo); return pc; } }; /*笔记本工厂单例,内部继承PCShop*/ var NoteBookShop = function(){ var F = function(){}; F.prototype.makeCPU = function(){ //实现自己的mkaeCPU方法,产生笔记本CPU return new NoteCPU(); }; F.extend(PCShop); var obj = new F(); return obj; }();上述代码中,PCShop为最基本的PC工厂,默认生产最基本的台式机,提供了一个公共的产生对象的接口createPC, 并提供了基本的硬件获得方法,NoteBookShop继承了PCShop,并实现了自己的CPU获得途径以适应笔记本,若哪天增加平板业务,我们也只需要增加一个PADShop类,并根据需要实现对应的接口。