introduce
Bridge mode separates abstract parts from its implementation parts so that they can all be varied independently.
text
The bridge mode is most commonly used in event monitoring. Let’s first look at a piece of code:
The code copy is as follows:
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);
});
}
There is a problem with the above code that getBeerById must have a browser context to be used, because it uses this.id property internally. If the context is not used, then it will be a stop. Therefore, generally a little experienced programmer will transform the program into the following form:
The code copy is as follows:
function getBeerById(id, callback) {
// Send the request via ID and return the data
asyncRequest('GET', 'beer.uri?id=' + id, function(resp) {
// callback response
callback(resp.responseText);
});
}
More practical, right? First, the ID can be passed in at will, and a callback function is also provided for custom processing functions. But what does this have to do with bridging? This is what the following code is going to reflect:
The code copy is as follows:
addEvent(element, 'click', getBeerByIdBridge);
function getBeerByIdBridge (e) {
getBeerById(this.id, function(beer) {
console.log('Requested Beer: '+beer);
});
}
Here, getBeerByIdBridge is the bridge we define, which is used to connect the abstract click event and getBeerById, and at the same time pass the ID of the event source and the customized call function (console.log output) into the getBeerById function as parameters.
This example looks a bit simple, let’s take another more complicated practical example.
Actual XHR connection queue
We want to build a queue, which stores many ajax requests in the queue. The use of queues is mainly because we need to ensure that the requests that are joined are processed first. At any time, we can pause requests, delete requests, retry requests, and support subscription events for each request.
Basic core functions
Before the official start, let’s define several core encapsulation functions. First, the first is the function encapsulation of asynchronous requests:
The code copy is as follows:
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;
};
})();
The encapsulated self-executing function is a general Ajax request function, and I believe that anyone with the attribute Ajax can understand it.
Next we define a general method to add methods (functions):
The code copy is as follows:
Function.prototype.method = function (name, fn) {
this.prototype[name] = fn;
return this;
};
Finally, add 2 methods about arrays, one for traversal and one for filtering:
The code copy is as follows:
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;
});
}
Because some new browsers already support these two functions (or some class libraries already support them), we must first judge if they are already supported, and if they are already supported, they will no longer be processed.
Observer system
Observers play an important role in the process of events in the queue and can subscribe to events when queued (success, failure, pending):
The code copy is as follows:
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);
}
);
}
};
The main implementation code of queue
First, you subscribe to the main attributes and event delegates of the queue:
The code copy is as follows:
DED.Queue = function () {
// Contains the queue for requests.
this.queue = [];
// Use Observable object on 3 different states so that you can subscribe to events at any time
this.onComplete = new DED.util.Observer;
this.onFailure = new DED.util.Observer;
this.onFlush = new DED.util.Observer;
// Core properties can be set during external calls
this.retryCount = 3;
this.currentRetry = 0;
this.paused = false;
this.timeout = 5000;
this.conn = {};
this.timer = {};
};
Then, through the chained call of DED.Queue.method, many available methods are added to the queue:
The code copy is as follows:
DED.Queue.
method('flush', function () {
// flush method
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 = [];
});
The code looks a lot, and after folding, you can find that it is actually defined on the queue with flush, setRetryCount, setTimeout, add, pause, dequeue, and clear methods.
Simple call
The code copy is as follows:
var q = new DED.Queue;
// Set the number of retrys a little higher to cope with slow connections
q.setRetryCount(5);
// Set timeout time
q.setTimeout(1000);
// Add 2 requests.
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 queue
q.flush();
// Pause the queue and save the remaining ones
q.pause();
// Clear.
q.clear();
// Add 2 requests.
q.add({
method: 'GET',
uri: '/path/to/file.php?ajax=true'
});
q.add({
method: 'GET',
uri: '/path/to/file.php?ajax=true&woe=me'
});
// Delete the last request from the queue.
q.dequeue();
// Flush again
q.flush();
Where is the bridge?
There is no bridge in the call code above, so what about the bridge? Take a look at the complete example below and you will find that there are bridges everywhere:
The code copy is as follows:
<!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 () {
// accomplish.
var q = new DED.Queue;
q.setRetryCount(5);
q.setTimeout(3000);
var items = $('items');
var results = $('results');
var queue = $('queue-items');
// Save track your request on the client
var requests = [];
// After each request for flush, subscribe to special processing steps
q.onFlush.subscribe(function (data) {
results.innerHTML = data;
requests.shift();
queue.innerHTML = requests.toString();
});
// Subscription time processing steps
q.onFailure.subscribe(function () {
results.innerHTML += ' <span style="color:red;">Connection Error!</span>';
});
// Subscribe to all successful processing steps 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>
Asynchronous join request</h1>
<div id="queue-items">
</div>
<div id="add-stuff">
<h2>Add a new request to the queue</h2>
<ul id="adders">
<li><a href="#" id="action-01">Add "01" to queue</a></li>
<li><a href="#" id="action-02">Add "02" to queue</a></li>
<li><a href="#" id="action-03">Add "03" to queue</a></li>
</ul>
</div>
<h2>Quote Control</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 Clear</a></li>
</ul>
<div id="results-area">
<h2>
result:
</h2>
<div id="results">
</div>
</div>
</div>
</body>
</html>
In this example, you can do various actions such as flush queues, pause queues, delete requests in queues, clear queues, etc. At the same time, I believe everyone has also experienced the power of bridges.
Summarize
The advantages of bridge mode are also obvious. We will only list a few main advantages:
1. Separate the interface and implementation parts. An implementation may not be bound to an interface invariably. The implementation of abstract class (function) can be configured at the runtime, and an object can even change its implementation at the runtime. It also fully decouples the abstraction and implementation, which is also conducive to layering, thereby generating a better structured system.
2. Improve scalability
3. Implementation details are transparent to customers and can hide implementation details from customers.
At the same time, the bridge mode also has its own disadvantages:
A large number of classes will lead to increased development costs and may also reduce performance.