5分钟读书笔记之 - 设计模式 - 工厂模式

一个类或者对象中,往往会包含别的对象。在创建这种对象的时候,你可能习惯于使用常规方式,即用 new 关键字和类构造函数。

这会导致相关的俩个类之间产生依赖。

工厂模式,就是消除这俩个类之间的依赖性的一种模式,它使用一种方法来决定究竟实例化那个具体的类。

简单工厂模式

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

var BicycleShop = function(){}
BicycleShop.prototype = {
    sellBicycle:function(model){
        var bicycle;
        switvh(model){
            case ‘The Speedster‘:
                bicycle = new Speedster();
                break;
            case ‘The Lowrider‘:
                bicycle = new Lowrider();
                break;
            case ‘The Comfort Cruiser‘:
            default:
                bicycle = new ComfortCruiser();
        }
        Interface.ensureImplements(bicycle,Bicycle);
        bicycle.assemble();
        bicycle.wash();
        return bicycle;
    }
}

sellBicycle 方法根据所要求的自行车型号用 switch 来创建实例。

Bicycle接口:

var Bicycle = new Interface(‘Bicycle‘,[‘assemble‘,‘wash‘,‘ride‘,‘repair‘]); 

Speedster 类:

var Speedster = function(){
    ...
};
Speedster.prototype = {
   assemble:fucntion(){
    ...
  },
  wash:fucntion(){
    ...
  },
  ride:fucntion(){
    ...
  },
  repair:fucntion(){
    ...
  }
}

var californiaCruisers = new BicycleShop();
var yourNewBike = 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 ComfortCruiser();
        }
        Interface.ensureImplements(bicycle,Bicycle);
        return bicycle;
    }
}

BicycleFactory是一个单体,用来把createBicycle封装在一个命名空间中,这个方法返回一个实现了Bicycle接口的对象。然后你可以照常对其进行组装和清洗。

var BicycleShop = function(){}
BicycleShop.prototype = {
    sellBicycle:function(model){
        var bicycle = BicycleFactory.createBicycle(model);
        bicycle.assemble();
        bicycle.wash();
        return bicycle;
    }
}

这样,有关提供车型的所有信息都集中到一个地方管理,所以添加更多车型很容易。

BicycleFactory 就是简单工厂的一个很好的例子,这种模式把成员对象的创建工作转交给一个外部对象。这个外部对象可以像本例一样是一个简单的命名空间,也可以是一个类的实例。

真正的工厂模式

真正的工厂模式与简单的工厂模式区别在于,它不是另外使用一个类或者对象来创建自行车,而是使用一个子类。

按照正常定义,工厂是一个将其成员对象的实例化推迟到子类中进行的类。

我们打算让自行车商店自己决定从那个生产厂家进货。出于这个原因,单单一个BicycleFactory对象将无法提供需要的所有自行车实例。我们可以把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.‘);
    }
}

以上类中定义了createBicycle方法,但是调用这个方法,会抛出一个错误,现在BicycleShop是一个抽象类,它不能被实例化,只能用来派生子类。

设计一个经销特定自行车生产厂家产品的子类需要扩展 BicycleShop 方法,下面是扩展方法。

var AcmeBicycleShop = function(){};
extend(AcmeBicycleShop,BicycleShop);
AcmeBicycleShop.prototype.createBicycle = function(model){
    var bicycle;
    switch(model){
    case ‘The Speedster‘:
        bicycle = new AcmSpeedster();
        break;
    case ‘The Lowrider‘:
        bicycle = new AcmLowrider();
        break;
    case ‘The Comfort Cruiser‘:
    default:
        bicycle = new AcmComfortCruiser();
    }
    Interface.ensureImplements(bicycle,Bicycle);
    return bicycle;
}

var alecsCruisers = new AcmeBicycleShop();
var yourNewBike = alecsCruisers.sellBicycle(‘The Lowrider‘);

对Bicycle进行的一般性操作的代码完全可以放在父类BicycleShop中,而对具体的Bicycle对象进行实例化的工作则被留到子类中。

这样,一般性的代码被集中在一个位置,而个体性的代码则被封装在子类中。

如果需要像前面那样,创建一些用不同方法实现同一接口的对象,可以使用简单工厂方法来简化-选择实现-的过程。这种选择可以是明确的,也可以是隐含的,明确如自行车例子,隐含的如下面的HXR实现:

在有些场合下,你通常要与一系列实现了同一接口,可以被同等对待的类打交道,这是js中使用工厂模式最常见的原因。

XHR 工厂实例:

用于发起请求的对象是某种类的实例,具体是哪种类取决于用户的浏览器。如果代码中需要多次执行Ajax请求,那么明智的做法就是把创建这种对象提取到一个类中,并创建一个包装器来包装在实际发起请求时所要经历的一系列步骤。简单工厂非常适合这种场合,它可以根据浏览器能力的不同生成一个XMLHttpRequest或 ActiveXObject 实例。

var AjaxHandler = new Interface(‘AjaxHandler‘,[‘request‘,‘createXhrObject‘]);

var SimpleHandler = function(){};
SimpleHandler.prototype = {
    request:function(method,url,callback,postVars){
        var xhr = this.createXhrObject();
        xhr.onreadystatechange = function(){
            if(xhr.readyState!==4) return;
            (xhr.status===200)?
                callback.success(xhr.responseText,xhr.responseXML):
                callback.failure(xhr.status);
        }
        xhr.open(method,url,true);
        if(method!==‘POST‘) postVars=null;
        xhr.send(postVars);
    },
    createXhrObject:function(){
        var methods = [
            function(){ return new XMLHttpRequest();},
            function(){ return new ActiveXObject(‘Msxml2.XMLHTTP‘);},
            function(){ return new ActiveXObject(‘Microsoft.XMLHTTP‘);}
        ];

        for(var i=0,len=methods.length;i<len;i++){
            try{
                methods[i]();
            }catch(e){
                continue;
            }
            //这里比较有意思,是一种记忆方式,代码执行一次之后,createXhrObject函数改变。
            this.createXhrObject = methods[i];
            return methods[i];
        }
        throw new Error(‘SimpleHandler:could not create xhr object‘);
    }
}

上面这例子可以进一步扩展,把工厂模式用在俩个地方,以便根据网络条件创建专门的请求对象。在创建XHR对象的时候已经使用过了简单工厂模式。另一个工厂则用来返回各种处理器类,他们都派生自SimpleHandler。

首先要做的是创建俩个新的处理器类

QueueHandler 会在发起新请求之前确保所有的请求都成功处理。

OfflineHandler 则会在用户处于离线状态是把请求缓存起来。

var QueuedHandler = function(){
    this.queue = [];
    this.requestInProgress = false;
    this.retryDelay = 5;
}

extend(QueuedHandler,SimpleHandler);

QueuedHandler.prototype.request = function(method,url,callback,postVars,override){
    //如果前面的请求还在处理,则直接把这些参数推入数组
    if(this.requestInProgress && !override){
        this.queue.push({
            method:method,
            url:url,
            callback:callback,
            postVars:postVars
        });
    }else{
        //如果处理完成,则执行该操作
        //把状态设置为处理中。。。。
        //创建对象
        this.requestInProgress = true;
        var xhr = this.createXhrObject();
        var _this = this;
        xhr.onreadystatechange = function(){
            if(xhr.readyState!==4) return;

            if(xhr.status===200){
                //请求成功,执行callback函数
                callback.success(xhr.responseText,xhr.responseXML);
                //继续处理队列中的请求
                _this.advanceQueue();
            }else{
                //请求失败,则每隔5秒进行一次请求
                callback.failure(xhr.status);
                setTimeout(function(){
                    _this.request(method,url,callback,postVars,override);
                },_this.retryDelay*1000);
            };
        };
        xhr.open(method,url,true);
        if(method!==‘POST‘) postVars=null;
        xhr.send(postVars);
    }
};

QueuedHandler.prototype.advanceQueue = function(){
    if(this.queue.length===0){
        this.requestInProgress = false;
        return;
    }
    var req = this.queue.shift();
    this.request(req.method,req.url,req.callback,req.postVars,true);
}

QueuedHandler 的 request 方法与SimpleHandler的看上去差不多,但是允许发起新的请求之前先检查一下,以确保当前没有别的请求正在处理。

OfflineHandler要更简单一点:

var OfflineHandler = function(){
    this.storedRequests = [];
}
extend(OfflineHandler,SimpleHandler);
OfflineHandler.prototype.request = function(method,url,callback,postVars){
    if(xhrManager.isOffline()){
        this.storedRequests.push({
            method:method,
            url:url,
            callback:callback,
            postVars:postVars
        });
    }else{
        this.flushStoredRequests();
        OfflineHandler.superclass.request(method,url,callback,postVars);
    };
    OfflineHandler.prototype.flushStoredRequests = function(){
        for(var i=0,len=storedRequests.length;i<len;i++){
            var req = storedRequests[i];
            OfflineHandler.superclass.request(req.method,req.url,req.callback,req.postVars);
        }
    }
}

xhrManager.isOffline 方法的作用在于判断用户是否处于在线状态。

现在用到工厂模式了,因为程序员根本根本不肯知道各个最终用户实际面临的网络条件,所以不可能要求他们在开发过程中选择使用哪个处理器类,而是应该用一个工厂在运行时选择最合适的类。

var xhrManager = {
    createXhrHandler:function(){
        var xhr;
        if (this.isOffline()) {
            xhr = new offlineHandler();
        }else if(this.isHighLatency()){
            xhr = new QueuedHandler();
        }else{
            xhr = new SimpleHandler();
        };

        Interface.ensureImplements(xhr,AjaxHandler);
        return xhr;
    },
    isOffline:function(){},
    isHighLatency:function(){}
}

现在程序员就可以使用这个工厂方法,而不必实例化一个特定的类了:

var myHandler = xhrManager.createXhrHandler();
var callback = {
    success:function(responseText){},
    failure:function(statusCode){}
}
myHandler.request(‘GET‘,‘script.php‘,callback);

示例:RSS阅读器:

RSS 阅读器对象,它的成员对象包括一个XHR处理器对象,一个显示对象,一个配置对象,XHR处理器类我们使用上面的xhrManager.createXhrHandler方法所创建的处理器对象,下面是一个显示类(显示对象):

var DisplayModule = new Interface(‘DisplayModule‘,[‘append‘,‘remove‘,‘clear‘]);

var ListDisplay = function(id,parent){
    this.list = document.createElement(‘ul‘);
    this.list.id = id;
    parent.appendChild(this.list);
}
ListDisplay.prototype = {
    append:function(text){
        var newEl = document.createElement(‘li‘);
        this.list.appendChild(newEl);
        newEl.innerHTML = text;
        return newEl;
    },
    remove:function(el){
        this.list.removeChild(el);
    },
    clear:function(){
        this.list.innerHTML = ‘‘;
    }
}

下面是一个配置对象,这只是一个对象字面量,包含一些供阅读器以及成员对象使用的设置:

var conf = {
    id:‘cnn-top-stories‘,
    feedUrl:‘http://.....rss‘,
    updateInterval:60,
    parent:$(‘feed-readers‘)
}

这些类由FeedReader组合使用。它使用XHR获取数据,并解析,最后显示模块将信息输出到网页:

var FeedReader = function(display,xhrHandler,conf){
    this.display = display;
    this.xhrHandler = xhrHandler;
    this.conf = conf;
    this.startUpdates();
}
FeedReader.prototype  = {
    fetchFeed:function(){
        var _this = this;
        var callback = {
            success:function(text,xml){
                _this.parseFeed(text,xml);
            },
            failure:function(status){
                _this.showError(status);
            }
        };
        this.xhrHandler.request(‘GET‘,this.conf.feedUrl,callback);
    },
    parseFeed:function(responseText,responseXML){
        this.display.clear();
        var items = responseXML.getElementsByTagName(‘item‘);
        for(var i= 0,len=items.length;i<len;i++){
            var title = items[i].getElementsByTagName(‘title‘)[0];
            var link = items[i].getElementsByTagName(‘link‘)[0];
            this.display.append(‘<a href="‘+link.firstChild.data+‘">‘+title.firstChild.data+‘</a>‘);
        }
    },
    showError:function(status){
        this.display.clear();
        this.display.append(‘Error fetching feed.‘);
    },
    stopUpdates:function(){
        clearInterval(this.interval);
    },
    startUpdates:function(){
        this.fetchFeed();
        var _this = this;
        this.interval = setInterval(function(){_this.fetchFeed();},
                this.conf.updateInterval*1000);
    }
}

现在还差一个部分,即把所有这些类和对象拼装起来的那个工厂方法,他被实现为一个简单的工厂:

var FeedManager = {
    createFeedReader:function(conf){
        var displayModule = new ListDisplay(conf.id+‘-display‘,conf.parent);
        Interface.ensureImplements(displayModule,DisplayModule);

        var xhrHandler = xhrManager.createXhrHandler();
        Interface.ensureImplements(xhrHandler,AjaxHandler);
        return new FeedReader(displayModule,xhrHandler,conf);
    }
}

使用API的程序员当然可以手工创建一个FeedReader对象,而不必借助FeedManager.createFeedReader方法,但是使用这个工厂方法,可以把FeedReader类所需要的复杂设置封装取来,并且可以确保其成员对象都实现了所需接口。

总结:

工厂模式主要用于消除对象间的耦合。通过使用工厂方法,而不是new关键字以及具体类,你可以把所有实例化代码集中在一个位置。

使用工厂模式,你可以先创建一个抽象的父类,然后在子类中创建工厂方法,从而把成员对象的实例化推迟到更专门化的子类中进行。

有人不禁把工厂方法当做万金油,把构造函数扔在一边。这并不值得提倡。

如果根本不可能另外换用一个类,或者不需要在运行期间在一系列可互换的类中进行选择,那就不应该使用工厂方法。

它主要用于所实例化的类型不能在开发期确定,而只能在运行期间确定的情况,此外,如果存在许多具有复杂的设置开销的相关对象,或者想创建一个包含了一些成员对象的类但是又想避免它们紧密的偶合在一起的话,就应该使用工厂模式。

5分钟读书笔记之 - 设计模式 - 工厂模式

时间: 2024-10-08 18:01:11

5分钟读书笔记之 - 设计模式 - 工厂模式的相关文章

5分钟读书笔记之 - 设计模式 - 桥接模式

补充一点知识: 私有变量 在对象内部使用'var'关键字来声明,而且它只能被私有函数和特权方法访问.私有函数 在对象的构造函数里声明(或者是通过var functionName=function(){...}来定义),它能被特权函数调用(包括对象的构造函数)和私有函数调用.特权方法 通过this.methodName=function(){...}来声明而且可能被对象外部的代码调用.可以使用:this.特权函数() 方式来调用特权函数,使用 :私有函数()方式来调用私有函数.公共属性 通过thi

5分钟读书笔记之 - 设计模式 - 门面模式

门面模式有俩个作用: 简化类的接口 消除类与使用它的客户代码之间的耦合 在javascript中,门面模式常常是开发人员最亲密的朋友.它是几乎所有javascript库的核心原则,门面模式可以使库提供的工具更容易理解.使用这种模式,程序员可以间接地与一个子系统打交道,与直接访问子系统相比,这样做更不容易出错. addEvent函数是一个基本的门面,你不用在每次为一个元素添加事件监听器的时候都得针对浏览器间的差异进行检查,有了这个便利,你可以把这个添加事件的底层细节抛在脑后,而把心思集中在如何构建

5分钟读书笔记之 - 设计模式 - 单体模式

单体是一个用来划分命名空间,并将一批相关方法和属性组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次. 单体模式,就是将代码组织为一个逻辑单元,这个逻辑单元中的代码可以通过单一的变量进行访问. 单体基本结构是这样: var Singleton = { attribute1:true, attribute2:10, method1:function(){}, method2:function(){} } 借助闭包实现单体: Namespace.Singleton = {} 定义之后立即执

5分钟读书笔记之 - 设计模式 - 组合模式

组合模式是一种专为创建Web上的动态用户界面而量身定制的模式,使用这种模式,可以用一条命令在对各对象上激发复杂的或递归的行为. 在组合对象的层次体系中有俩种类型对象:叶对象和组合对象.这是一个递归定义,但这正是组合模式如此有用的原因所在.一个组合对象由一些别的组合对象和叶对象组成,其中只有叶对象不再包含子对象,叶对象是组合对象中最基本的元素,也是各种操作的落实地点. 存在一批组织成某种层次体系的对象(具体的结构在开发期间可能无法得知) 希望这批对象或其中的一部分对象实施一个操作 表单验证实例:

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

创建型:Factory(工厂模式) 说到工厂,我们最新想到的应该是一堆堆的原料通过流水线组装成产品的场景吧? 其实对于一个程序员来讲,这个模式应该是很难不遇到的,特别是对于web开发的人员.为啥呢?因为这种场景在我们使用MVC的M层中是经常会遇到的.Hibernate的SessionFactory和JPA标准的EntityFactory. 所以说工厂模式用人类的语言来沟通的话,就是你给我清单上的原材料,我给你组装好的产品.非常简单的一个Demo(简单工厂): package top.gabin.

5分钟读书笔记之 - 设计模式 - 命令模式

本章研究的是一种封装方法调用的方式.命令模式与普通函数有所不同.它可以用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行. 它也可以用来消除调用操作的对象和实现操作的对象之间的耦合.这为各种具体的类的更换带来了极大的灵活性.这种模式可以用在许多不同的场合,不过它在创建用户界面这一方面非常有用,特别是在需要不受限的取消操作的时候.它还可以用来替代回调函数,因为它能够提高在对象之间传递的操作的模块化程度. 命令的结构: 最简形式的命令对象是一个操作和用以调用这个操作

5分钟读书笔记之 - 设计模式 - 装饰者模式

本章讨论的是一种为对象增添特性的技术,它并不使用创建新子类这种手段. 装饰者模式可以透明地把对象包装在具有同样接口的另一对象之中,这样一来,你可以给一些方法添加一些行为,然后将方法调用传递给原始对象.相对于创建子类来说,使用装饰者模式对象是一种更灵活的选择. 装饰者可用于为对象增加功能.它可以用来替代大量子类. 考虑前面的自行车类,你现在可能提供一些配件供用户选择,装饰者模式要求我们只需要创建选件类,这些类与四种自行车类都要实现Bicycle接口,但是他们只被用作这些自行车类的包装类.在这个例子

大话设计模式读书笔记1——简单工厂模式

最近几日,重温了一下<大话设计模式>这本书,当时读的仓促,有很多没有注意的地方,现在仔细翻看起来,发现这值得细细品味的一本书!! 好东西就要记下来!!! 第一章笔记:从一个简单的计算器程序来看简单工厂模式. 变化的地方就要封装,用一个单独的类来做创造实例的过程这就是工厂. UML图: /// <summary> /// 运算类 /// </summary> public class Operation { public double Number1 { get; set

5分钟读书笔记之 - 设计模式 - 适配器模式

适配器模式可以用来在现在接口和不兼容的类之间进行适配. 使用这种模式的对象又叫包装器,因为他们是在用一个新接口包装另一个对象. 在设计类的时候往往遇到有些接口不能与现有api一同使用的情况,借助于适配器,你可以不用直接修改这些类也能使用他们. 适配器的特点: 适配器可以被添加到现有代码中以协调俩个不同的接口.从表面上来看,适配器模式很像门面模式,他们都要对别的对象进行包装并改变其呈现的接口,二者之间的差别在于他们如何改变接口.门面元素展现的是一个简化接口,它并不提供额外的选择,而且有时是为了方便