桥接&组合

桥接

实现 API 的时候,桥接模式非常有用,可能正是由于这个,该模式使用地不够广泛.在设计 js API 时,该模式可以弱化API 与使用它的类和对象之间的耦合.
该模式的作用在于**将抽象与其实现隔离开,让他们独立变化**.而且对于事件驱动编程有许多好处.

js API有 getter, setter, requester 以及其他基于动作的方法.无论它们是用来创建 Web 鼓舞 API 还是普通 accessor 和 mutator.在实现过程中桥接模式都有助于保持 API 代码的简洁.

事件监听器

最常见的应用场合之一是事件监听回调.假设有一个 API 函数 getBeerById, 他根据一个标识符返回有关啤酒的信息.

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

这个 API 只能工作在浏览器中,根据事件监听器回调函数的机制,事件对象自然会被作为第一个参数传递给这个函数,在本例中没有使用这个参数,只是从 this 对象获取 id.那么如果在命令行环境运行它就失效不起作用.

function getBeerById(id, callback) {
    // Make request for beer by ID, then return the beer data.
    asyncRequest('GET', 'beer.url?id=' + id, function (resp) {
        // callback reponse
        callback(resp.responseText);
    });
}

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

这样就只针对接口而不是实现进行编程,用桥接模式把抽象隔离开来.
有了这层桥接元素,API 的适用范围大大拓宽,现在的 getBeerById 没有和事件对象绑定在一起,可以在单元测试中运行该 API,只需提供一个 ID 和对调函数.现在可以在命令行环境运行这个接口.

其他例子

除了在事件回调函数与接口之间进行桥接外,桥接模式还可以用连接公开的 API代码和私有的实现代码.另外,还可以连接多个类.从类的角度看,意味着把接口作为公开的代码编写,把类的实现作为私有代码编写.
可以使用一些特权方法来做桥梁以便访问私有变量空间.第3章已经见到过.

var Public = function () {
    var secretNum = 3;
    this.privilegedGetter = function () {
        return secretNum;
    };
};
var p = new Public();
var data = p.privilegedGetter();

连接多个类:

var Class1 = function (a, b, c) {
    this.a = a;
    this.b = b;
    this.c = c;
};
var Class2 = function (d) {
    this.d = d;
}
var BridgeClass = function (a, b, c, d) {
    this.one = new Class1(a, b, c);
    this.two = new Class2(d);
}

在这里,桥接类是一个门面类,不同的是桥接模式能够让Class1和 Class2独立于 BridgeClass 而发生变化,和门面类不同.

适用场合

事件驱动编程必备.不过我们喜欢事件驱动开发的函数式风格,却忘了编写接口.判断什么时候是用桥接模式很简单:

$('#example-id').on('click', function () {
    new RichTextEditor();
})l

现在没法看出编辑器要显示在哪,有什么配置项,还有如何修改.要做的是让接口'可桥接'(可适配)...

还有就是之前讲到的特权函数用来访问私有数据.

让 API 更强壮,提高组件的模块化程度.把抽象和其实现隔离开,有助于独立管理,而且Bug 更容易查找.

每使用一个桥接元素都要增加一次函数调用,提高系统复杂度,影响性能,不要滥用,举例来说,如果一个桥接函数被用于连接两个函数,但是其中某个函数根本不会在桥接函数之外被调用,那么该桥接函数可以不用.

----------next part----------

组合

同一条命令在多个对象上引发复杂或者递归的操作.
两大好处:

  1. 用同样的方法处理对象集合和其中的特定子对象.组合对象 composite 和组成它的对象实现了同批操作.是一种向下传递的操作性质.

  2. 把一批子对象组织成树形结构,使整棵树都可以被遍历.所有组合对象都实现了一个用来获取子对象的方法.

组合对象的结构

组合对象的层次体系中有两种类型的对象:叶对象和组合对象,是一个递归定义:一个组合对象由一些别的组合对象和叶对象组成.只有叶对象不再包含子对象,它是组合对象中最基本的元素,也落实了各种操作.(树是数据结构中较为基本的概念,这里的概念应该不难理解的)

使用

必须同时具备两个条件:

  • 存在一批组织成某种层次体系的对象

  • 希望对这批对象或者其中一部分对象进行一个共同操作.
    组合模式擅长于对大批对象进行操作.组织这类对象并把操作从一个层次向下一个层次传递,可以弱化对象间的耦合并且可以互换的使用一些类或者实例.

示例:表单验证

要求创建一个表单,可以保存,恢复,验证值.看似不复杂,但是主要问题在于表单中元素的内容和数目完全未知,而且因用户而异.紧密耦合到 Name 和 Address 特定表单域的 validate 函数不会起作用,因为无法验证哪些域...
现在是组合模式大展身手的时候,首先,我们要逐个验证表单的各个组成元素,判断属于组合对象还是叶对象.表单最基本的构成要素是用户用于输入数据的域,由input, select,textarea 等等组成.上面一层是用于组织域的 fieldset 标签.最顶层是表单自身.

叶对象 (input)   --|                        |
                   -- 组合对象 (filedset)    |
叶对象 (input)   --|                        |
                                           |=组合对象 (form)
叶对象 (select)  --|                        |
                   --组合对象 (fieldset)     |
叶对象 (textarea)--|                        |

组合对象和其所有子对象都具有相同的接口,可能有人会把他们看成超类和子类的关系,但是并非如此,叶对象没有继承上一级组合对象.

首先是创建一个动态表单并且实现save 和 validate 操作.表单中实际拥有的域因用户而异,所以 save,validate 函数不可能满足每个人需要,不过可以设计一个模块化表单,以便将来任何时候都能为其添加各种元素,不用修改 save 和 validate 函数.

不用为表单元素的每一种可能组合编写一套方法,应该让这两个方法和表单域自身关联起来.让每个域都可以保存和验证自己:

nameFieldset.validate();
nameFieldset.save();

难点在于如何同时在所有域上执行这些操作,使用组合模式来简化代码:要保存所有域,只需一次调用:topForm.save();topForm 对象将会在所有子对象上递归调用 save 方法,实际的 save 操作只会发生在底层的叶对象上.组合对象志气到了一个传递调用的作用.
实现代码:
首先,创建组合对象和叶对象需要实现的两个接口:

var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
var FormItem = new Interface('FormItem', ['save']);

目前 FormItem 只要求实现一个 save 函数,稍后会对其进行扩充.
CompositeForm 类的代码如下:

var CompositeForm = function(id, method, action) { // implements Composite, Formitem
    this.formComponents = [];

    this.element = document.createElement('form');
    this.element.id = id;
    this.element.method = method || 'POST';
    this.element.action = action || '#';
};

CompositeForm.prototype.add = function (child) {
    Interface.ensureImplements(child, Composite, FormItem);
    this.formComponents.push(child);
    this.element.appendChild(child.getElement());
};

CompositeForm.prototype.remove = function (child) {
    for (var i = 0, len = this.formComponents.length; i < len; i++) {
        if (this.formComponents[i] === child) {
            this.formComponents.splice(i, 1); // Remove one element from the array at position i.
            break;
        }
    }
};

CompositeForm.prototype.getChild = function (i) {
    return this.formComponents[i];
};
CompositeForm.prototype.remove = function (child) {
    for (var i = 0, len = this.formComponents.length; i < len; i++) {
        this.formComponents[i].save();
    }
};

CompositeForm.prototype.getElement = function () {
    return this.element;
};

CompositeForm 的子对象保存在一个数组中.使用 Interface.ensureImplements 是为了保证要添加到组合对象中的对象实现了正确的接口.
现在看看叶对象类:

var Field = function (id) { // implements Composite, FormItem
    this.id = id;
};

Field.prototype.add = function () {};
Field.prototype.remove = function () {};
Field.prototype.getChild = function () {};

Field.prototype.save = function () {
    setCookie(this.id, this.getValue());
};

Field.prototype.getElement = function () {
    return this.element;
};

Field.prototype.getValue = function () {
    throw new Error('Unsupported operation on the class Field.');
};

这个类将被各个叶对象类继承.它将 Composite 接口中的方法实现为空函数,因为叶节点不会有子对象.

这里最简单地实现了 save 方法.但是把用户原始数据放在 Cookie 是一个非常糟糕的做法.因为 Cookie 很容易被篡改,所以数据的有效性得不到保证.其次,存储在 Cookie 中的数据有大小限制.所以用户的数据可能不会被全部保存下来.
最后,还会影响性能,因为每次请求中 Cookie 都会作为 http 头被一起发送.

save 方法用 getValue 方法获得所要保存的对象值,getValue() 方法各个子类中的实现各不相同.使用 save 方法,不用提交表单也能保存表单的内容.
这个对于长表单来说很有用,因为用户可以不用填完表单中途保存, 然后忙完其他事情再来完成表单的填写:

var InputField = function(id, label) { // implements Composite, FormItem
    Field.call(this, id);

    this.input = document.createElement('input');
    this.input.id = id;

    this.label = document.createElement('label');
    var labelTextNode = document.createTextNode(label);
    this.label.appendChild(lavelTExtNode);


    this.element = document.createElement('div');
    this.element.className = 'input-field';
    this.label.appendChild(this.label);
    this.label.appendChild(this.input);
};
extend(InputField, Field); // Inherit from Field.
InputField.prototype.getValue = function () {
    return this.input.value;
};

InputField 是 Field 的子类之一.大多数方法都是从Field 继承而来.但是他也实现了针对 input 标签的 getValue 方法的代码.TextareaField 和 SelectField 也实现了自己特有的 getValue 方法.

var TextareaField = function(id, label) { // implements Composite, FormItem
    Field.call(this, id);

    this.textarea = document.createElement('textarea');
    this.textarea.id = id;

    this.label = document.createElement('label');
    var labelTextNode = document.createElement('select');
    this.select.id = id;

    this.element = document.createElement('div');
    this.element.className = 'input-field';
    this.element.appendChild(this.label);
    this.element.appendChild(this.select);
};
extend(SelectField, Field); // Inherit from Field.

SelectField.prototype.getValue = function () {
    return this.select.options[this.select.selectedIndex].value;
};

使用组合模式,简单的操作也能产生复杂的结果.不必编写大量手工遍历数据或者其他数据结构的粘合代码,只需对最顶层的对象执行操作,让每一个子对象自己传递这个操作.
组合模式中,各个对象之间的耦合非常松散.只要他们实现了同样的接口,那么改变他们的位置或者互相交换很简单.促进了代码得宠用,也有利于代码重构.
用组合模式组织起来的对象形成了一个出色的层次体系.每当对顶层对象执行一个操作时,实际上是在对整个结构进行深度优先搜索以查找节点.在该层次体系中添加,删除,查找节点都很容易.

组合对象的易用性可能掩盖了它所支持的每一种操作的代价.由于对组合对象调用的任何操作都会被传递到他的所有子对象,那么如果层次体系很大,系统的性能就会受到影响.topGallery.show()这样一个方法的调用会引起一次对整个数结构的遍历.

小结

它将一批子对象组织为树形结构,只要一条命令就可以操作树中的所有对象.他提高了代码的模块化程度,而且便于代码重构和对象的替换.这种模式特别适用于动态的 HTML 用户界面,在他的帮助下,可以在不知道用户界面的最终格局的情况下进行开发.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值