среда, 30 декабря 2009 г.

Bridge design pattern with JavaScript

http://www.codeproject.com/KB/scripting/jbridge.aspx

Foreword

Thanks to those of you who take the time to read through the article. If you feel like dropping off a vote (and particularly if it's a low one), please include a comment which mentions what the problem was. I've been getting mostly high votes for this article, apart from the odd 1's or 2's, and I'd really like to know what bothered those voters the most. Feedback is what drives improvement.

This article assumes you are familiar with object aspects of the JavaScript language. If not, it is recommended that you read the first half of Salvatore Vetro's article.

This article is organized as follows:

Problem

Graphic content is the thing every web developer deals with in every single page created. Problems begin when it goes a little further than placing an <IMG> tag. You may wish to build a web-based graphics editor, game, ad banner, interactive teaser, etc.

The first solution that comes to mind is Flash. You can satisfy almost any web graphics need with it, but disadvantages can be prohibitive. They are: need for investments in docs, tools, and programmers; deficient support for in-browser scripting; incompatibility with numerous web media standards, problems with Flash Player security; etc.

XML-based standards are the alternative. They offer perfect web integration (as XML does), are extensible, and do not require downloading large binary archives. The only drawback is that XML is 'still' an emerging technology, so you'll meet numerous incompatibility issues when dealing with XML grammars.

To start with web graphics, we'll need something simple like this:

Collapse
<script type="text/javascript">

var vgr = new VectorGraphics(...);

vgr.MoveTo(100, 100);
vgr.Rect(100, 100, "#0F0");

vgr.MoveTo(350, 350);
vgr.Circle(50, "#F0F");

...

</script>

As usual, there is more than one way to do the job. To be exact, the number of ways is the number of browsers, plug-ins, and standards (cross-multiplied with each other...) on the web. To satisfy the needs of all (or, at least, the majority of) your customers, you must pass all these ways... Do you want to meet the multiple-page-browser-and-platform-detection-stuff (with all those switches and ifs so easy to get lost in) once again? I don't.

So, before we move on with rectangles and circles, we'll have to answer two not-so-simple questions:

  • how do we build an object library which will take the best of two (three, four...) worlds, if choices are available?
  • how do we build an object library which is easy to extend and maintain?

The answer is:

Bridge pattern

Note: JavaScript is a prototype-based language, and doesn't support many features common to object-oriented languages. Where it makes sense, I'll make notes describing differences and sideways; they are designated as [JavaScript].

  • Motivation

    Consider the implementation of a cross-browser vector graphics library. Its main purpose is to allow us to use an (X)HTML page as a painting canvas, so we can draw various shapes: lines, rectangles, circles, etc. It also should enable us to write scripts that work on any browser that supports either SVG or VML standards. Using inheritance ([JavaScript]: prototype chaining), we could define an interface VectorGraphics, and classes VectorGraphicsImplSVG and VectorGraphicsImplVML that implement the given interface:

    This approach has two problems:

    1. It is inconvenient to extend the VectorGraphics abstraction to cover more specialized kinds of vector graphics API, or new platforms. Imagine a GeoGraphics subclass of VectorGraphics that specializes the vector graphics to display GIS data. To support GeoGraphics for both SVG and VML user agents, we have to implement two new classes, GeoGraphicsImplSVG and GeoGraphicsImplVML. Worse, we'll have to define two classes for every new XML-based graphics standard. Supporting a third API requires yet another new vector graphics subclass for every specific case of VectorGraphics. Every framework extension leads to the explosive growth of class/prototype hierarchy:

    2. Once the supporting programmer has GeoGraphicsImplSVG and GeoGraphicsImplVML, what class should he/she instantiate? It is clear that the VectorGraphics class itself must "decide" what class to present to the end-user. In other words, clients should be able to instantiate a VectorGraphics object without committing to a concrete implementation. Only the implementation classes should depend on the platform on which the script runs. Therefore, client code should instantiate VectorGraphics without mentioning specific graphics standards.

    The Bridge pattern solves the problem by putting the VectorGraphics abstraction and all its implementations in separate class/prototype hierarchies. There is one hierarchy for VectorGraphics interfaces (VectorGraphics, GeoGraphics, UIGraphics), and a separate hierarchy for platform-specific implementations, with VectorGraphicsImpl as its root (see details below). The VectorGraphicsImplSVG, for example, provides an implementation based on the SVG rendering subsystem.

    Such a relationship between VectorGraphics and VectorGraphicsImpl is called a bridge, because it bridges the interface and its implementation, letting them vary independently.

    (C++ gurus can say: "bridge pattern adds one 'level of indirection', just like a Proxy pattern".)

  • Applicability

    Use the Bridge pattern when:

    • you want to explicitly separate the interface and the implementation for a custom object. This allows you to choose (and even switch) implementors at run-time
    • both the abstractions and their implementations should be extensible (by either subclassing or prototype chaining). This prevents the uncontrolled growth of class hierarchy, and allows you to evade cumulative coding errors (changes on the implementor classes have no impact on the interface classes). That is why the Bridge pattern is great when used as a base of cross-platform frameworks
    • you want to localize platform-specific code, thus making your scripts easy to extend and maintain
    • you want to localize browser/feature/object detection code. (Note: you may or may not want to do it; the structure of the bridge pattern forces you to do it.)

  • Structure

  • Participants

    • Abstraction

      • defines the abstraction's interface
      • maintains a reference to an implementor object
    • RefinedAbstraction

      • extends the interface defined by abstraction
    • Implementor

      • defines the interface for implementation classes. This interface may or may not be the same as the abstraction's.

      Note: since JavaScript is a loosely-typed language with no notion of abstract classes, Implementor is uncommon to JavaScript implementations of the Bridge pattern. It is usual to have a set of ConcreteImplementors; you set or change the abstraction's implementor reference to one of the ConcreteImplementors during the setup of the bridge. All ConcreteImplementors must conform to a chosen interface, as if all concrete implementors were subclasses of an Implementor.

    • ConcreteImplementor
      • implements the Implementor interface and defines its concrete implementation.

  • Collaborations

    The abstraction forwards client requests to its Implementor object.


Комментариев нет:

Отправить комментарий