Javascript 桥接模式的理论与实战教程

基本理论

桥接模式定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
桥接模式主要有4个角色组成:
(1)抽象类
(2)扩充抽象类
(3)实现类接口
(4)具体实现类
根据javascript语言的特点,我们将其简化成2个角色:
(1)扩充抽象类
(2)具体实现类
怎么去理解桥接模式呢?我们接下来举例说明

桥接模式的实现

理解桥接模式的思想,关键是要理解其分离抽象部分和实现部分的思想。我们举例进行说明

最简单的桥接模式

其实我们最经常用的jQuery的each函数就是一个典型的桥接模式,我们模拟其实现如下:

var each = function (arr, fn) {
    for (var i = 0; i < arr.length; i++) {
        var val = arr[i];
        if (fn.call(val, i, val, arr)) {
            return false;
        }
    }
}
var arr = [1, 2, 3, 4];
each(arr, function (i, v) {
    arr[i] = v * 2;
})

在这个例子中,我们通过each函数循环了arr数组,别看这个例子很常见,但其中就包含了典型的桥接模式。

在这个例子中,抽象部分是each函数,也就是上面说的扩充抽象类,实现部分是fn,即具体实现类。抽象部分和实现部分可以独立的进行变化。这个例子虽然简单,但就是一个典型的桥接模式的应用。

插件开发中的桥接模式

桥接模式的一个适用场景是组件开发。我们平时开发组件为了适应不同场合,组件相应的会有许多不同维度的变化。桥接模式就可以应用于此,将其抽象与实现分离,使组件的扩展性更高。
假设我们要开发一个弹窗插件,弹窗有不同的类型:普通消息提醒,错误提醒,每一种提醒的展示方式还都不一样。这是一个典型的多维度变化的场景。首先我们定义两个类:普通消息弹窗和错误消息弹窗。

function MessageDialog(animation) {
    this.animation = animation;
}
MessageDialog.prototype.show = function () {
    this.animation.show();
}
function ErrorDialog(animation) {
    this.animation = animation;
}
ErrorDialog.prototype.show = function () {
    this.animation.show();
}

这两个类就是前面提到的抽象部分,也就是扩充抽象类,它们都包含一个成员animation。

两种弹窗通过show方法进行显示,但是显示的动画效果不同。我们定义两种显示的效果类如下:

function LinerAnimation() {
}
LinerAnimation.prototype.show = function () {
    console.log("it is liner");
}
function EaseAnimation() {
}
EaseAnimation.prototype.show = function () {
    console.log("it is ease");
}

这两个类就是具体实现类,它们实现具体的显示效果。那我们如何调用呢?

var message = new MessageDialog(new LinerAnimation());
message.show();
var error = new ErrorDialog(new EaseAnimation());
error.show();

如果我们要增加一种动画效果,可以再定义一种效果类,传入即可。

总结

学习桥接模式关键是要理解抽象部分与实现部分的分离,使得二者可以独立的变化,而不必拘泥于形式。JS插件灵活的变化,适用场景的多变就非常适合使用这种模式来实现。使用桥接模式最重要的是要找出系统中不同的变化维度。

深入理解JavaScript设计模式之桥接模式

桥接模式最常用在事件监控上,先看一段代码:

addEvent(element, 'click', getBeerById);
function getBeerById(e) {
var id = this.id;
asyncRequest('GET', 'beer.uri?id=' + id, function(resp) {
// Callback response.
console.log('Requested Beer: ' + resp.responseText);
});
}

上述代码,有个问题就是getBeerById必须要有浏览器的上下文才能使用,因为其内部使用了this.id这个属性,如果没用上下文,那就歇菜了。所以说一般稍微有经验的程序员都会将程序改造成如下形式:

function getBeerById(id, callback) {
// 通过ID发送请求,然后返回数据
asyncRequest('GET', 'beer.uri?id=' + id, function(resp) {
// callback response
callback(resp.responseText);
});
}

实用多了,对吧?首先ID可以随意传入,而且还提供了一个callback函数用于自定义处理函数。但是这个和桥接有什么关系呢?这就是下段代码所要体现的了:

addEvent(element, 'click', getBeerByIdBridge);
  function getBeerByIdBridge (e) {
    getBeerById(this.id, function(beer) {
      console.log('Requested Beer: '+beer);
  });
}

这里的getBeerByIdBridge就是我们定义的桥,用于将抽象的click事件和getBeerById连接起来,同时将事件源的ID,以及自定义的call函数(console.log输出)作为参数传入到getBeerById函数里。

这个例子看起来有些简单,我们再来一个复杂点的实战例子。

实战XHR连接队列

我们要构建一个队列,队列里存放了很多ajax请求,使用队列(queue)主要是因为要确保先加入的请求先被处理。任何时候,我们可以暂停请求、删除请求、重试请求以及支持对各个请求的订阅事件。

基础核心函数

在正式开始之前,我们先定义一下核心的几个封装函数,首先第一个是异步请求的函数封装:

var asyncRequest = (function () {
    function handleReadyState(o, callback) {
        var poll = window.setInterval(
                    function () {
                        if (o && o.readyState == 4) {
                            window.clearInterval(poll);
                            if (callback) {
                                callback(o);
                            }
                        }
                    },
                    50
                    );
    }

    var getXHR = function () {
        var http;
        try {
            http = new XMLHttpRequest;
            getXHR = function () {
                return new XMLHttpRequest;
            };
        }

        catch (e) {
            var msxml = [
                        'MSXML2.XMLHTTP.3.0',
                        'MSXML2.XMLHTTP',
                        'Microsoft.XMLHTTP'
                        ];

            for (var i = 0, len = msxml.length; i < len; ++i) {
                try {
                    http = new ActiveXObject(msxml[i]);
                    getXHR = function () {
                        return new ActiveXObject(msxml[i]);
                    };
                    break;
                }
                catch (e) { }
            }
        }
        return http;
    };

    return function (method, uri, callback, postData) {
        var http = getXHR();
        http.open(method, uri, true);
        handleReadyState(http, callback);
        http.send(postData || null);
        return http;
    };
})();

上述封装的自执行函数是一个通用的Ajax请求函数,相信属性Ajax的人都能看懂了。

接下来我们定义一个通用的添加方法(函数)的方法:

Function.prototype.method = function (name, fn) {
    this.prototype[name] = fn;
    return this;
};

最后再添加关于数组的2个方法,一个用于遍历,一个用于筛选:

if (!Array.prototype.forEach) {
    Array.method('forEach', function (fn, thisObj) {
        var scope = thisObj || window;
        for (var i = 0, len = this.length; i < len; ++i) {
            fn.call(scope, this[i], i, this);
        }
    });
}

if (!Array.prototype.filter) {
    Array.method('filter', function (fn, thisObj) {
        var scope = thisObj || window;
        var a = [];
        for (var i = 0, len = this.length; i < len; ++i) {
            if (!fn.call(scope, this[i], i, this)) {
                continue;
            }
            a.push(this[i]);
        }
        return a;
    });
}

因为有的新型浏览器已经支持了这两种功能(或者有些类库已经支持了),所以要先判断,如果已经支持的话,就不再处理了。

观察者系统

观察者在队列里的事件过程中扮演着重要的角色,可以队列处理时(成功、失败、挂起)订阅事件:

window.DED = window.DED || {};
DED.util = DED.util || {};
DED.util.Observer = function () {
    this.fns = [];
}

DED.util.Observer.prototype = {
    subscribe: function (fn) {
        this.fns.push(fn);
    },

    unsubscribe: function (fn) {
        this.fns = this.fns.filter(
            function (el) {
                if (el !== fn) {
                    return el;
                }
            }
            );
            },
    fire: function (o) {
        this.fns.forEach(
            function (el) {
                el(o);
            }
            );
    }
};

队列主要实现代码

首先订阅了队列的主要属性和事件委托:

DED.Queue = function () {
    // 包含请求的队列.
 this.queue = [];
    // 使用Observable对象在3个不同的状态上,以便可以随时订阅事件
 this.onComplete = new DED.util.Observer;
    this.onFailure = new DED.util.Observer;
    this.onFlush = new DED.util.Observer;

    // 核心属性,可以在外部调用的时候进行设置
 this.retryCount = 3;
    this.currentRetry = 0;
    this.paused = false;
    this.timeout = 5000;
    this.conn = {};
    this.timer = {};
};

然后通过DED.Queue.method的链式调用,则队列上添加了很多可用的方法:

DED.Queue.
    method('flush', function () {
        // flush方法
 if (!this.queue.length > 0) {
            return;
        }

        if (this.paused) {
            this.paused = false;
            return;
        }

        var that = this;
        this.currentRetry++;
        var abort = function () {
            that.conn.abort();
            if (that.currentRetry == that.retryCount) {
                that.onFailure.fire();
                that.currentRetry = 0;
            } else {
                that.flush();
            }
        };

        this.timer = window.setTimeout(abort, this.timeout);
        var callback = function (o) {
            window.clearTimeout(that.timer);
            that.currentRetry = 0;
            that.queue.shift();
            that.onFlush.fire(o.responseText);
            if (that.queue.length == 0) {
                that.onComplete.fire();
                return;
            }

            // recursive call to flush
 that.flush();

        };

        this.conn = asyncRequest(
            this.queue[0]['method'],
            this.queue[0]['uri'],
            callback,
            this.queue[0]['params']
            );
    }).
    method('setRetryCount', function (count) {
        this.retryCount = count;
    }).
    method('setTimeout', function (time) {
        this.timeout = time;
    }).
    method('add', function (o) {
        this.queue.push(o);
    }).
    method('pause', function () {
        this.paused = true;
    }).
    method('dequeue', function () {
        this.queue.pop();
    }).
    method('clear', function () {
        this.queue = [];
    });

代码看起来很多,折叠以后就可以发现,其实就是在队列上定义了flush, setRetryCount, setTimeout, add, pause, dequeue, 和clear方法。

简单调用

var q = new DED.Queue;
// 设置重试次数高一点,以便应付慢的连接
q.setRetryCount(5);
// 设置timeout时间
q.setTimeout(1000);
// 添加2个请求.
q.add({
    method: 'GET',
    uri: '/path/to/file.php?ajax=true'
});

q.add({
    method: 'GET',
    uri: '/path/to/file.php?ajax=true&woe=me'
});

// flush队列
q.flush();
// 暂停队列,剩余的保存
q.pause();
// 清空.
q.clear();
// 添加2个请求.
q.add({
    method: 'GET',
    uri: '/path/to/file.php?ajax=true'
});

q.add({
    method: 'GET',
    uri: '/path/to/file.php?ajax=true&woe=me'
});

// 从队列里删除最后一个请求.
q.dequeue();
// 再次Flush
q.flush();

桥接呢?

上面的调用代码里并没有桥接,那桥呢?看一下下面的完整示例,就可以发现处处都有桥哦:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>Ajax Connection Queue</title>
    <script src="utils.js"></script>
    <script src="queue.js"></script>
    <script type="text/javascript">
        addEvent(window, 'load', function () {
            // 实现.
var q = new DED.Queue;
            q.setRetryCount(5);
            q.setTimeout(3000);
            var items = $('items');
            var results = $('results');
            var queue = $('queue-items');
            // 在客户端保存跟踪自己的请求
var requests = [];
            // 每个请求flush以后,订阅特殊的处理步骤
            q.onFlush.subscribe(function (data) {
                results.innerHTML = data;
                requests.shift();
                queue.innerHTML = requests.toString();
            });
            // 订阅时间处理步骤
            q.onFailure.subscribe(function () {
                results.innerHTML += ' <span style="color:red;">Connection Error!</span>';
            });
            // 订阅全部成功的处理步骤x
            q.onComplete.subscribe(function () {
                results.innerHTML += ' <span style="color:green;">Completed!</span>';
            });
            var actionDispatcher = function (element) {
                switch (element) {
                    case 'flush':
                        q.flush();
                        break;
                    case 'dequeue':
                        q.dequeue();
                        requests.pop();
                        queue.innerHTML = requests.toString();
                        break;
                    case 'pause':
                        q.pause();
                        break;
                    case 'clear':
                        q.clear();
                        requests = [];
                        queue.innerHTML = '';
                        break;
                }
            };
            var addRequest = function (request) {
                var data = request.split('-')[1];
                q.add({
                    method: 'GET',
                    uri: 'bridge-connection-queue.php?ajax=true&s=' + data,
                    params: null
                });
                requests.push(data);
                queue.innerHTML = requests.toString();
            };
            addEvent(items, 'click', function (e) {
                var e = e || window.event;
                var src = e.target || e.srcElement;
                try {
                    e.preventDefault();
                }
                catch (ex) {
                    e.returnValue = false;
                }
                actionDispatcher(src.id);
            });
            var adders = $('adders');
            addEvent(adders, 'click', function (e) {
                var e = e || window.event;
                var src = e.target || e.srcElement;
                try {
                    e.preventDefault();
                }
                catch (ex) {
                    e.returnValue = false;
                }
                addRequest(src.id);
            });
        });
    </script>
    <style type="text/css" media="screen">
        body
        {
            font: 100% georgia,times,serif;
        }
        h1, h2
        {
            font-weight: normal;
        }
        #queue-items
        {
            height: 1.5em;
        }
        #add-stuff
        {
            padding: .5em;
            background: #ddd;
            border: 1px solid #bbb;
        }
        #results-area
        {
            padding: .5em;
            border: 1px solid #bbb;
        }
    </style>
</head>
<body id="example">
    <div id="doc">
        <h1>
            异步联接请求</h1>
        <div id="queue-items">
        </div>
        <div id="add-stuff">
            <h2>向队列里添加新请求</h2>
            <ul id="adders">
                <li><a href="#" id="action-01">添加 "01" 到队列</a></li>
                <li><a href="#" id="action-02">添加 "02" 到队列</a></li>
                <li><a href="#" id="action-03">添加 "03" 到队列</a></li>
            </ul>
        </div>
        <h2>队列控制</h2>
        <ul id='items'>
            <li><a href="#" id="flush">Flush</a></li>
            <li><a href="#" id="dequeue">出列Dequeue</a></li>
            <li><a href="#" id="pause">暂停Pause</a></li>
            <li><a href="#" id="clear">清空Clear</a></li>
        </ul>
        <div id="results-area">
            <h2>
                结果:
            </h2>
            <div id="results">
            </div>
        </div>
    </div>
</body>
</html>

在这个示例里,你可以做flush队列,暂停队列,删除队列里的请求,清空队列等各种动作,同时相信大家也体会到了桥接的威力了。

总结

桥接模式的优点也很明显,我们只列举主要几个优点:

    分离接口和实现部分,一个实现未必不变地绑定在一个接口上,抽象类(函数)的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现,同将抽象和实现也进行了充分的解耦,也有利于分层,从而产生更好的结构化系统。
    提高可扩充性
    实现细节对客户透明,可以对客户隐藏实现细节。

同时桥接模式也有自己的缺点:

大量的类将导致开发成本的增加,同时在性能方面可能也会有所减少。

时间: 2024-10-05 01:10:00

Javascript 桥接模式的理论与实战教程的相关文章

详解JavaScript设计模式开发中的桥接模式使用_基础知识

桥接模式将抽象部分与实现部分分离开来,使两者都可以独立的变化,并且可以一起和谐地工作.抽象部分和实现部分都可以独立的变化而不会互相影响,降低了代码的耦合性,提高了代码的扩展性. 按照GoF的定义,桥接模式的作用在于"将抽象与其实现隔离开来,以便二者独立变化".这种模式对于Javascript中常见的事件驱动的编程大有裨益. 桥接模式最常见和实际的应用场合之一是事件监听器回调函数. example:事件监听器,把事件处理的语句封装到回调函数中,通过接口而不是实现进行编程. 基本理论 桥接

Javascript设计模式理论与实战:桥接模式

桥接模式将抽象部分与实现部分分离开来,使两者都可以独立的变化,并且可以一起和谐地工作.抽象部分和实现部分都可以独立的变化而不会互相影响,降低了代码的耦合性,提高了代码的扩展性. 基本理论 桥接模式定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化. 桥接模式主要有4个角色组成: (1)抽象类 (2)扩充抽象类 (3)实现类接口 (4)具体实现类 根据javascript语言的特点,我们将其简化成2个角色: (1)扩充抽象类 (2)具体实现类 怎么去理解桥接模式呢?我们接下来举例说明 桥接

Javascript设计模式理论与实战:简单工厂模式

通常我们创建对象最常规的方法就是使用new关键字调用构造函数,这会导致对象之间的依赖性.工厂模式是一种有助于消除类之间依赖性的设计模式,它使用一个方法来决定要实例化哪一个类.本文详细介绍了简单工厂模式的理论,并且举例说明了简单工厂模式的具体应用. 基本介绍 简单工厂模式是工厂模式中最基本的一种.通过定义一个工厂类,根据参数实例化具体的某个产品类. 举例说明 我们举个例子进行说明:假设我们开发一个旅游行业网站,网站上面销售机票,酒店等产品.一个用户准备购买一张机票.我们可以定义相关类如下: var

深入理解JavaScript系列(44):设计模式之桥接模式详解_基础知识

介绍 桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化. 正文 桥接模式最常用在事件监控上,先看一段代码: 复制代码 代码如下: addEvent(element, 'click', getBeerById); function getBeerById(e) { var id = this.id; asyncRequest('GET', 'beer.uri?id=' + id, function(resp) { // Callback response. consol

《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.2 桥接模式

2.2 桥接模式 "桥接模式"(Bridge Pattern)用于将"抽象"(abstraction,比如接口或算法)与实现方式相分离. 如果不用桥接模式,那么通常的写法是,创建若干个基类,用于表示各种抽象方式,然后从每个基类中继承出两个或多个子类,用于表示对这种抽象方式的不同实现办法.用了桥接模式之后,我们需要创建两套独立的"类体系"(class hierarchy):"抽象体系"定义了我们所要执行的操作(比如接口或高层算法

C# 设计模式系列教程-桥接模式_C#教程

1. 概述 将抽象部分(Abstraction)与实现部分(Implementor)分离,使它们可以独立地变化. 2. 解决的问题 在软件系统中,有些类型由于自身的逻辑,它具有两个或多个维度的变化.为了解决这种多维度变化,又不引入复杂度,这就要使用Bridge模式. 3. 模式中的角色 2.1 抽象(Abstraction):定义抽象接口,该接口中包含实现具体行为.具体特征的Implementor接口. 2.2 提炼的抽象(RefinedAbstraction):继承自Abstraction的子

SEO搜索中文分词算法原理实战教程

SEO搜索中文分词算法原理实战教程,如果一个人想成为一个合格的SEO人员,那么搜索引擎分词思维是必须掌握的,因为只有掌握了分词思维,你才可以做出搜索引擎喜欢,而且用户也喜欢的网页,虽然在梦想之旅视频教程中有和大家分享过搜索引擎中文分词原理,但没有完全的,系统的用文字版本和图片版本和大家分享,那么顾芳源就带大家如何正确学习SEO搜索分词思维吧. 搜索引擎中文分词原理 首先我们要知道搜索引擎工作原理是把每个网页的内容按词来录入到数据库,比如你的文章标题是:梦想SEO实战培训提供免费SEO教程,那么搜

mybatis实战教程(mybatis in action),mybatis入门到精通

 目录(?)[-] mybatis实战教程mybatis in action之一开发环境搭建 mybatis实战教程mybatis in action之二以接口的方式编程 mybatis实战教程mybatis in action之三实现数据的增删改查 mybatis实战教程mybatis in action之四实现关联数据的查询 mybatis实战教程mybatis in action之五与spring3集成附源码 mybatis实战教程mybatis in action之六与Spring

《Unity 3D NGUI 实战教程》一2.5 创建第一个UI

2.5 创建第一个UI Unity 3D NGUI 实战教程 2.5.1 创建一个2D UI 制作UI时,首先我们要创建UI的"根".在Unity顶部NGUI菜单中选择Create,然后选择2D UI,如图2.25所示. 创建完成后,我们能看到图2.26所示的景象,在Scene窗口中,NGUI自动生成了一个名为UI Root的物体,其中带有一个Camera作为子物体. 这个新生成的Camera,是NGUI生成的专门用来渲染UI的相机,当我们生成NGUI的UI Root时,就自动将生成的