JS工厂模式介绍

第一篇文章: 

工厂方法模式(Factory method pattern)


    工厂方法模式(Factory method pattern)是一种实现“工厂”概念的面上对象设计模式。实质是定义一个创建对象的接口,但是让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。
    创建一个对象常常需要复杂的过程,所以不适合在一个复杂的对象中。创建对象可能会导致大量的重复代码,也可能提供不了足够级别的抽象。工厂方法模式通过定义一个单独的创建对象的方法来解决这些问题,由子类实现这个方法来创建具体类型的对象。

  1. //几个Button类
  2. class Button{/* ...*/}
  3. class WinButton extends Button{/* ...*/}
  4. class MacButton extends Button{/* ...*/}
  5.  
  6. //它们的工厂类
  7. interface ButtonFactory{
  8.     abstract Button createButton();
  9. }
  10. class WinButtonFactory implements ButtonFactory{
  11.     Button createButton(){
  12.         return new WinButton();
  13.     }
  14. }

  15. class MacButtonFactory implements ButtonFactory{
  16.     Button createButton(){
  17.         return new MacButton();
  18.     }
  19. }

    在JS中创建对象会习惯的使用 new 关键字和类构造函数(当然主要还是对象字面量),问题在于这样会导致两个类之间产生依赖性。
    工厂模式就是一种有助于消除两个类依赖性的模式。
    简单工厂模式:使用一个类(通常为单体)来生成实例。
    复杂工厂模式:使用子类来决定一个成员变量应该是哪个具体的类的实例。

实现1:简单工厂模式


    上 JS设计模式 上的实例, 对于自行车商店出售自行车:

  1. /* BicycleShop class */
  2. var BicycleShop = function(){};
  3. BicycleShop.prototype = {

  4.     sellBicycle : function( model ){
  5.         var bicycle;
  6.         switch( model ){
  7.             case "The Speedster":
  8.                 bicycle = new Speedster();
  9.                 break;
  10.             case "The Lowrider":
  11.                 bicycle = new Lowrider();
  12.                 break;
  13.             case "The Cruiser":
  14.             default:
  15.                 bicycle = new Cruiser();
  16.                 break;
  17.         }
  18.         return bicycle;
  19.     }
  20. }
    
    sellBicycle 方法根据所提供的自行车型号来进行自行车的实例创建。那么对于一家ABC店铺,需要Speedster车型我只需要
    var ABC = new BicycleShop();
    var myBike = ABC.sellBicycle("The Speedster");


    以上方式很管用,但是一旦说我需要添加一些自行车款式的时候我就必须修改 BicycleShop 的 switch 部分,那么只要是修改就有可能带来BUG。所以,将这部分生成实例的代码单独的提出来分工交给一个简单的工厂对象是一个很不错的方法。


  1. var BicycleFactory = {
  2.     createBicycle : function( model ){
  3.         var bicycle;
  4.         switch( model ){
  5.             case "The Speedster":
  6.                 bicycle = new Speedster();
  7.                 break;
  8.             case "The Lowrider":
  9.                 bicycle = new Lowrider();
  10.                 break;
  11.             case "The Cruiser":
  12.             default:
  13.                 bicycle = new Cruiser();
  14.                 break;
  15.         }
  16.         return bycicle;
  17.     }
  18. }

    BicycleFactory 是一个脱离于BicycleShop的单体。降低耦合度的效果显而易见。当需要添加新的类型的时候,不需要动 BicycleShop 只需修改工厂单体对象就可以。

  1. var BicycleShop = function(){};

  2. BicycleShop.prototype = {
  3.     sellBicycle : function( model ){
  4.         var bicycle = BicycleFactory.createBicycle(model);     
  5.         return bicycle;
  6.     }
  7. }
   
     以上就是一个很好的 简单工厂模式 的实例。该模式将成员对象的创建工作交给一个外部对象实现,该外部对象可以是一个简单的命名空间,也可以是一个类的实例。


实现2:工厂模式


    真正的工厂模式与简单工厂模式相比,主要区别就是它不是另外使用一个对象或者类来创建实例(自行车),而是使用一个子类。工厂是一个将其成员对象的实例化推迟到子类中进行的类。


    比如加入BicycleShop可以决定从那一家厂商进行进货,那么简单的一个BicycleFactory是不够了的,因为各个厂商会各自生产不同的Speedster,Lowrider,Cruiser等型号自行车,所以首先需要生成各自厂商的shop实例,不同厂商的shop实例拥有不同的生成几个型号自行车的方法。

    也就是相当于将自行车对象的实例化推迟到了shop实例中产生。
    基础:
    
  1. var BicycleShop = function(){}

  2. BicycleShop.prototype={
  3.     sellBicycle: function( model ){
  4.         var bicycle = this.createBicycle( model );
  5.         return bicycle;
  6.     },
  7.     createBicycle: function( model ){
  8.         throw new Error( " Unsupported " );
  9.     }
  10. }

    各自厂商:
    
  1. var AcmeBicycleShop = function(){};

  2. extend( AcmeBicycleShop , BicycleShop );
  3. AcmeBicycleShop.prototype.createBicycle = function( model ){
  4.     var bicycle;
  5.     switch( model ){
  6.         case "The Speedster":
  7.             bicycle = new AcmeSpeedster();
  8.             break;
  9.         case "The Lowrider":
  10.             bicycle = new AcmeLowrider();
  11.             break;
  12.         case "The Cruiser":
  13.         default:
  14.             bicycle = new AcmeCruiser();
  15.             break;
  16.     }
  17.     return bicycle;
  18. }

  19. var GeneralBicycleShop = function(){};

  20. extend( GeneralBicycleShop , BicycleShop );
  21. GeneralBicycleShop.prototype.createBicycle = function( model ){
  22.    ...
  23. }

    那么接下来就很简单 对于来自 Acme 进货的 

    var acmeShop = new AcmeBicycleShop();
    var newBicycle = acmeShop.sellBicycle("The Speedster");

    当然,你也可以对于外层生成的子类实例在使用简单工厂模式进行包装一下~对于添加其他厂商也很简单,在创建一个Bicycle的子类重新定义其createBicycle的工厂方法即可。


    工厂模式使用场合


    1. 动态实现
    例如自行车的例子,创建一些用不同方式实现统一接口的对象,那么可以使用一个工厂方法或者简单工厂对象来简化实现过程。选择可以是明确进行的也可以是隐含的。

    2. 节省设置开销
    如果对象要进行复杂的并且彼此相关的设置的时候,那么工厂模式可以很显著的减少每种对象的代码量。将特定的设置代码提取出来会使得代码有极大地提升。并且能优化结构便于维护。

    3. 用于许多小型对象组成一个大对象。

    4. 工厂模式之利
    主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字。将所有实例化的代码集中在一个位子防止代码重复。

    5. 工厂模式之弊

    大多数类最好使用new关键字和构造函数,可以让代码更加简单易读。而不必去查看工厂方法来知道。


第二篇文章:

什么是面向对象?面向对象是一种思想!(废话)。

  面向对象可以把程序中的关键模块都视为对象,而模块拥有属性及方法。这样我们如果把一些属性及方法封装起来,日后使用将非常方便,也可以避免繁琐重复的工作。接下来将为大家讲解在JS中面向对象的实现。

   工厂模式

  工厂模式是软件工程领域一种广为人知的设计模式,而由于在ECMAScript中无法创建类,因此用函数封装以特定接口创建对象。其实现方法非常简单,也就是在函数内创建一个对象,给对象赋予属性及方法再将对象返回即可。

?
1
2
3
4
5
6
7
8
9
10
11
function createBlog(name, url) {
   var o = new Object();
   o.name = name;
   o.url = url;
   o.sayUrl= function () {
     alert( this .url);
   }
   return o;
}
  
var blog1 = createBlog( 'wuyuchang' , 'http://www.jb51.net/' );

可以看到工厂模式的实现方法非常简单,解决了创建多个相似对象的问题,但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,因此出现了构造函数模式。

  构造函数模式

  ECMAScript中构造函数可以创建特定类型的对象,类似于Array、Date等原生JS的对象。其实现方法如下:

?
1
2
3
4
5
6
7
8
9
10
function Blog(name, url) {
   this .name = name;
   this .url = url;
   this .alertUrl = function () {
     alert( this .url);
   }
}
  
var blog = new Blog( 'wuyuchang' , 'http://www.jb51.net/' );
console.log(blog instanceof Blog);  // true, 判断blog是否是Blog的实例,即解决了工厂模式中不能

这个例子与工厂模式中除了函数名不同以外,细心的童鞋应该发现许多不同之处:

函数名首写字母为大写  (虽然标准没有严格规定首写字母为大写,但按照惯例,构造函数的首写字母用大写
没有显示的创建对象
直接将属性和方法赋值给了this对象
没有return语句
使用new创建对象
能够识别对象(这正是构造函数模式胜于工厂模式的地方)

  构造函数虽然好用,但也并非没有缺点,使用构造函数的最大的问题在于每次创建实例的时候都要重新创建一次方法(理论上每次创建对象的时候对象的属性均不同,而对象的方法是相同的),然而创建两次完全相同的方法是没有必要的,因此,我们可以将函数移到对象外面(也许有些童鞋已经看出缺点,嘘!)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Blog(name, url) {
   this .name = name;
   this .url = url;
   this .alertUrl = alertUrl;
}
  
function alertUrl() {
   alert( this .url);
}
  
var blog = new Blog( 'scjb51' , 'http://sc.jb51.net/' ),
   blog2 = new Blog( 'jb51' , 'http://www.jb51.net/' );
blog.alertUrl();  // http://sc.jb51.net/
blog2.alertUrl();  // http://www.jb51.net/

我们将alertUrl设置成全局函数,这样一来blog与blog2访问的都是同一个函数,可是问题又来了,在全局作用域中定义了一个实际只想让Blog使用的函数,显示让全局作用域有些名副其实,更让人无法接受的是在全局作用域中定义了许多仅供特定对象使用的方法,浪费空间不说,显然失去了面向对象封装性了,因此可以通过原型来解决此问题。

  原型模式

  我们创建的每个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处就是可以让所有对象实例共享它所包含的属性及方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Blog() {
}
  
Blog.prototype.name = 'wuyuchang' ;
Blog.prototype.url = 'http://tools.jb51.net/' ;
Blog.prototype.friend = [ 'fr1' , 'fr2' , 'fr3' , 'fr4' ];
Blog.prototype.alertInfo = function () {
   alert( this .name + this .url + this .friend );
}
  
// 以下为测试代码
var blog = new Blog(),
   blog2 = new Blog();
blog.alertInfo();  // wuyuchanghttp://tools.jb51.net/fr1,fr2,fr3,fr4
blog2.alertInfo();  // wuyuchanghttp://tools.jb51.net/fr1,fr2,fr3,fr4
  
blog.name = 'wyc1' ;
blog.url = 'http://***.com' ;
blog.friend.pop();
blog2.name = 'wyc2' ;
blog2.url = 'http://+++.com' ;
blog.alertInfo();  // wyc1http://***.comfr1,fr2,fr3
blog2.alertInfo();  // wyc2http://+++.comfr1,fr2,fr3

原型模式也不是没有缺点,首先,它省略了构造函数传递初始化参数这一环节,结果所有实例在默认情况下都取得了相同的属性值,这样非常不方便,但这还是不是原型的最大问题,原型模式的最大问题在于共享的本性所导致的,由于共享,因此因此一个实例修改了引用,另一个也随之更改了引用。因此我们通常不单独使用原型,而是结合原型模式与构造函数模式。

  混合模式(原型模式 + 构造函数模式)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Blog(name, url, friend) {
   this .name = name;
   this .url = url;
   this .friend = friend;
}
  
Blog.prototype.alertInfo = function () {
   alert( this .name + this .url + this .friend);
}
  
var blog = new Blog( 'wuyuchang' , 'http://tools.jb51.net/' , [ 'fr1' , 'fr2' , 'fr3' ]),
   blog2 = new Blog( 'wyc' , 'http://**.com' , [ 'a' , 'b' ]);
  
blog.friend.pop();
blog.alertInfo();  // wuyuchanghttp://tools.jb51.net/fr1,fr2
blog2.alertInfo();  // wychttp://**.coma,b

混合模式中构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。每个实例都会有自己的一份实例属性,但同时又共享着方法,最大限度的节省了内存。另外这种模式还支持传递初始参数。优点甚多。这种模式在ECMAScript中是使用最广泛、认同度最高的一种创建自定义对象的方法。

  动态原型模式

  动态原型模式将所有信息封装在了构造函数中,而通过构造函数中初始化原型(仅第一个对象实例化时初始化原型),这个可以通过判断该方法是否有效而选择是否需要初始化原型。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Blog(name, url) {
   this .name = name;
   this .url = url;
  
   if ( typeof this .alertInfo != 'function' ) {
     // 这段代码只执行了一次
     alert( 'exe time' );
     Blog.prototype.alertInfo = function () {
       alert(thia.name + this .url);
     }
   }
}
  
var blog = new Blog( 'wuyuchang' , 'http://tools.jb51.net' ),
   blog2 = new Blog( 'wyc' , 'http:***.com' );

可以看到上面的例子中只弹出一次窗,'exe time',即当blog初始化时,这样做blog2就不在需要初始化原型,对于使用这种模式创建对象,可以算是perfect了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值