JavaScript设计模式深入分析(1)(4)
门面(facade)模式:门面模式是几乎所有JavaScript库的核心原则
子系统中的一组接口提供一个一致的界面,门面模式定义了一个高层接口,这个接口使得这一子系统更加容易使用,简单的说这是一种组织性的模式,它可以用来修改类和对象的接口,使其更便于使用。
门面模式的两个作用:1.简化类的接口;2.消除类与使用它的客户代码之间的耦合。
门面模式的使用目的就是图方面。
想象一下计算机桌面上的那些快捷方式图标,它们就是在扮演一个把用户引导至某个地方的接口的角色,每次操作都是间接的执行一些幕后的命令。
你在看这篇的博客的时候我就假设你已经有JavaScript的使用经验了,那么你一定写过或者看过这样的代码:
- var addEvent = function(el,type,fn){
- if(window.addEventListener){
- el.addEventListener(type,fn);
- }else if(window.attachEvent){
- el.attachEvent('on'+type,fn);
- }else{
- el['on'+type] = fn;
- }
- }
这个就是一个JavaScript中常见的事件监听器函数,这个函数就是一个基本的门面,有了它,就有了为DOM节点添加事件监听器的简便方法。
现在要说门面模式的精华部分了,为什么说JavaScript库几乎都会用这种模式类。假如现在要设计一个库,那么最好把其中所有的工具元素放在一起,这样更好用,访问起来更简便。看代码:
- //_model.util是一个命名空间
- _myModel.util.Event = {
- getEvent:function(e){
- return e|| window.event;
- },
- getTarget:function(e){
- return e.target||e.srcElement;
- },
- preventDefault:function(e){
- if(e.preventDefault){
- e.preventDefault();
- }else{
- e.returnValue = false;
- }
- }
- };
- //事件工具大概就是这么一个套路,然后结合addEvent函数使用
- addEvent(document.getElementsByTagName('body')[0],'click',function(e){
- alert(_myModel.util.Event.getTarget(e));
- });
个人认为,在处理游览器差异问题时最好的解决办法就是把这些差异抽取的门面方法中,这样可以提供一个更一致的接口,addEvent函数就是一个例子。
适配置器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作,使用这种模式的对象又叫包装器,因为他们是在用一个新的接口包装另一个对象。
从表面上看,它和门面模式有点相似,差别在于它们如何改变接口,门面模式展现的是一个简化的接口,它并不提供额外的选择,而适配器模式则要把一个接口转换为另一个接口,它并不会滤除某些能力,也不会简化接口。先来一个简单的示例看看:
- //假如有一个3个字符串参数的函数,但是现在拥有的却是一个包含三个字符串元素的对象,那么就可以用一个配置器来衔接二者
- var clientObject = {
- str1:'bat',
- str2:'foo',
- str3:'baz'
- }
- function interfaceMethod(str1,str2,str3){
- alert(str1)
- }
- //配置器函数
- function adapterMethod(o){
- interfaceMethod(o.str1, o.str2, o.str3);
- }
- adapterMethod(clientObject)
- //adapterMethod函数的作为就在于对interfaceMethod函数进行包装,并把传递给它的参数转换为后者需要的形式。
适配器模式的工作机制是:用一个新的接口对现有类得接口进行包装。
示例:适配两个库。下面的例子要实现的是从Prototype库的$函数到YUI的get方法的转换。
- //先看它们在接口方面的差别
- //Prototype $ function
- function $(){
- var elements = new Array();
- for(var i=0;i<arguments.length;i++){
- var element = arguments[i];
- if(typeof element == 'string'){
- element = document.getElementById(element);
- }
- if(typeof.length ==1) return element;
- elements.push(element);
- }
- return elements;
- }
- //YUI get method
- YAHOO.util.Dom.get = function(el){
- if(YAHOO.lang.isString(el)){
- return document.getElementById(el);
- }
- if(YAHOO.lang.isArray(el)){
- var c =[];
- for(var i=0,len=el.length;i<len;++i){
- c[c.length] = YAHOO.util.Dom.get(el[i]);
- }
- return c;
- }
- if(el){
- return el;
- }
- return null;
- }
- //二者区别就在于get具有一个参数,且可以是HTML,字符串或者数组;而$木有正是的参数,允许使用者传入任意数目的参数,不管HTML还是字符串。
- //如果需要从使用Prototype的$函数改为使用YUI的get方法(或者相反,那么用适配器模式其实很简单)
- function PrototypeToYUIAdapter(){
- return YAHOO.util.Dom.get(arguments);
- }
- function YUIToPrototypeAdapter(el){
- return $.apply(window,el instanceof Array?el:[el]);
- }
享元(Flyweight)模式:运用共享技术有效地支持大量细粒度的对象。
享元模式可以避免大量非常相似类的开销。在程序设计中有时需要生成大量细粒度的类实例来表示数据。如果发现这些实例除了几个参数外基本伤都是相同的,有时就能够受大幅度第减少需要实例化的类的数量。如果能把这些参数移到类实例外面,在方法调用时将他们传递进来,就可以通过共享大幅度地减少单个实例的数目。
从实际出发说说自己的理解吧。
组成部分
“享元”:抽离出来的外部操作和数据;
“工厂”:创造对象的工厂;
“存储器”:存储实例对象的对象或数组,供“享元”来统一控制和管理。
应用场景
1. 页面存在大量资源密集型对象;
2. 这些对象具备一定的共性,可以抽离出公用的操作和数据
关键
1. 合理划分内部和外部数据。
既要保持每个对象的模块性、保证享元的独立、可维护,又要尽可能多的抽离外部数据。
2. 管理所有实例
既然抽离出了外部数据和操作,那享元就必须可以访问和控制实例对象。在JavaScript这种动态语言中,这个需求是很容易实现的:我们可以把工厂生产出的对象简单的扔在一个数组中。为每个对象设计暴露给外部的方法,便于享元的控制。
优点
1. 将能耗大的操作抽离成一个,在资源密集型系统中,可大大减少资源和内存占用;
2. 职责封装,这些操作独立修改和维护;
缺点
1. 增加了实现复杂度。
将原本由一个工厂方法实现的功能,修改为了一个享元+一个工厂+一个存储器。
2. 对象数量少的情况,可能会增大系统开销。
示例:
- //汽车登记示例
- var Car = function(make,model,year,owner,tag,renewDate){
- this.make=make;
- this.model=model;
- this.year=year;
- this.owner=owner;
- this.tag=tag;
- this.renewDate=renewDate;
- }
- Car.prototype = {
- getMake:function(){
- return this.make;
- },
- getModel:function(){
- return this.model;
- },
- getYear:function(){
- return this.year;
- },
- transferOwner:function(owner,tag,renewDate){
- this.owner=owner;
- this.tag=tag;
- this.renewDate=renewDate;
- },
- renewRegistration:function(renewDate){
- this.renewDate=renewDate;
- }
- }
- //数据量小到没多大的影响,数据量大的时候对计算机内存会产生压力,下面介绍享元模式优化后
- //包含核心数据的Car类
- var Car=function(make,model,year){
- this.make=make;
- this.model=model;
- this.year=year;
- }
- Car.prototype={
- getMake:function(){
- return this.make;
- },
- getModel:function(){
- return this.model;
- },
- getYear:function(){
- return this.year;
- }
- }
- //中间对象,用来实例化Car类
- var CarFactory=(function(){
- var createdCars = {};
- return {
- createCar:function(make,model,year){
- var car=createdCars[make+"-"+model+"-"+year];
- return car ? car : createdCars[make + '-' + model + '-' + year] =(new Car(make,model,year));
- }
- }
- })();
- //数据工厂,用来处理Car的实例化和整合附加数据
- var CarRecordManager = (function() {
- var carRecordDatabase = {};
- return {
- addCarRecord:function(make,model,year,owner,tag,renewDate){
- var car = CarFactory.createCar(make, model, year);
- carRecordDatabase[tag]={
- owner:owner,
- tag:tag,
- renewDate:renewDate,
- car:car
- }
- },
- transferOwnership:function(tag, newOwner, newTag, newRenewDate){
- var record=carRecordDatabase[tag];
- record.owner = newOwner;
- record.tag = newTag;
- record.renewDate = newRenewDate;
- },
- renewRegistration:function(tag,newRenewDate){
- carRecordDatabase[tag].renewDate=newRenewDate;
- },
- getCarInfo:function(tag){
- return carRecordDatabase[tag];
- }
- }
- })();
- 上一篇:Node.js源码研究之模块组织加载
- 下一篇:JavaScript原型继承






