When we put up a facade, we present an outward appearance to the world that may conceal a very different reality. This was the inspiration for the name behind the next pattern weâre going to review: the Facade pattern. This pattern provides a convenient higher-level interface to a larger body of code, hiding its true underlying complexity. Think of it as simplifying the API being presented to other developers, something that almost always improves usability (Figure 9-8).
Facades are a structural pattern that can often be seen in JavaScript libraries such as jQuery where, although an implementation may support methods with a wide range of behaviors, only a âfacade,â or limited abstraction of these methods, is presented to the public for use.
This allows us to interact with the Facade directly rather than the
subsystem behind the scenes. Whenever we use jQueryâs $(el).css()
or $(el).animate()
methods, weâre actually using a
Facade: the simpler public interface that lets us avoid having to manually
call the many internal methods in jQuery core required to get some behavior working. This also
circumvents the need to manually interact with DOM APIs and maintain state
variables.
The jQuery core methods should be considered intermediate abstractions. The more immediate burden to developers is the DOM API, and facades are what make the jQuery library so easy to use.
To build on what weâve learned, the Facade pattern both simplifies the interface of a class and decouples the class from the code that uses it. This gives us the ability to indirectly interact with subsystems in a way that can sometimes be less prone to error than accessing the subsystem directly. A Facadeâs advantages include ease of use and often a small-sized footprint in implementing the pattern.
Letâs take a look at the pattern in action. This is an unoptimized code example, but here weâre using a Facade to simplify an interface for listening to events across browsers. We do this by creating a common method that can be used in oneâs code which does the task of checking for the existence of features so that it can provide a safe and cross-browser compatible solution.
var
addMyEvent
=
function
(
el
,
ev
,
fn
){
if
(
el
.
addEventListener
){
el
.
addEventListener
(
ev
,
fn
,
false
);
}
else
if
(
el
.
attachEvent
){
el
.
attachEvent
(
"on"
+
ev
,
fn
);
}
else
{
el
[
"on"
+
ev
]
=
fn
;
}
};
In a similar manner, weâre all familiar with jQueryâs $(document).ready(..)
. Internally, this is
actually powered by a method called bindReady()
, which is doing this:
bindReady
:
function
()
{
...
if
(
document
.
addEventListener
)
{
// Use the handy event callback
document
.
addEventListener
(
"DOMContentLoaded"
,
DOMContentLoaded
,
false
);
// A fallback to window.onload, that will always work
window
.
addEventListener
(
"load"
,
jQuery
.
ready
,
false
);
// If IE event model is used
}
else
if
(
document
.
attachEvent
)
{
document
.
attachEvent
(
"onreadystatechange"
,
DOMContentLoaded
);
// A fallback to window.onload, that will always work
window
.
attachEvent
(
"onload"
,
jQuery
.
ready
);
...
This is another example of a Facade, in which the rest of the world
simply uses the limited interface exposed by $(document).ready(..)
and the more complex
implementation powering it is kept hidden from sight.
Facades donât just have to be used on their own, however. They can also be integrated with other patterns, such as the Module pattern. As we can see below, our instance of the Module pattern contains a number of methods that have been privately defined. A Facade is then used to supply a much simpler API to accessing these methods:
var
module
=
(
function
()
{
var
_private
=
{
i
:
5
,
get
:
function
()
{
console
.
log
(
"current value:"
+
this
.
i
);
},
set
:
function
(
val
)
{
this
.
i
=
val
;
},
run
:
function
()
{
console
.
log
(
"running"
);
},
jump
:
function
(){
console
.
log
(
"jumping"
);
}
};
return
{
facade
:
function
(
args
)
{
_private
.
set
(
args
.
val
);
_private
.
get
();
if
(
args
.
run
)
{
_private
.
run
();
}
}
};
}());
// Outputs: "current value: 10" and "running"
module
.
facade
(
{
run
:
true
,
val
:
10
}
);
In this example, calling module.facade()
will actually trigger a set of
private behavior within the module, but again, the users arenât concerned
with this. Weâve made it much easier for them to consume a feature without
needing to worry about implementation-level details.
Facades generally have few disadvantages, but one concern
worth noting is performance. Namely, one must determine whether there is
an implicit cost to the abstraction a Facade offers to our
implementation and, if so, whether this cost is justifiable. Going back
to the jQuery library, most of us are aware that both getElementById("identifier")
and $("#identifier")
can be used to query an element on a page by its
ID.
Did you know however that getElementById()
on its own is significantly
faster by a high order of magnitude? Take a look at this jsPerf test to
see results on a per-browser level: http://jsperf.com/getelementbyid-vs-jquery-id. Now of
course, we have to keep in mind that jQuery (and Sizzle, its selector
engine) are doing a lot more behind the scenes to optimize our query
(and that a jQuery object, not just a DOM node is returned).
The challenge with this particular Facade is that in order to
provide an elegant selector function capable of accepting and parsing
multiple types of queries, there is an implicit cost of abstraction. The
user isnât required to access jQuery.getById("identifier")
or jQuery.getbyClass("identifier")
and so on.
That said, the trade-off in performance has been tested in practice over
the years and, given the success of jQuery, a simple Facade actually
worked out very well for the team.
When using the Facade pattern, try to be aware of any performance costs involved and make a call on whether they are worth the level of abstraction offered.
Get Learning JavaScript Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.