资讯专栏INFORMATION COLUMN

桥接&组合

yuanxin / 2898人阅读

摘要:桥接实现的时候桥接模式非常有用可能正是由于这个该模式使用地不够广泛在设计时该模式可以弱化与使用它的类和对象之间的耦合该模式的作用在于将抽象与其实现隔离开让他们独立变化而且对于事件驱动编程有许多好处有以及其他基于动作的方法无论它们是用来创建鼓

桥接
实现 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----------

组合

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

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

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

组合对象的结构

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

使用

必须同时具备两个条件:

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

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

示例:表单验证

要求创建一个表单,可以保存,恢复,验证值.看似不复杂,但是主要问题在于表单中元素的内容和数目完全未知,而且因用户而异.紧密耦合到 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 用户界面,在他的帮助下,可以在不知道用户界面的最终格局的情况下进行开发.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/80460.html

相关文章

  • python设计模式-桥接模式&amp;比较桥接模式和装饰模式的不同

    摘要:桥接模式和装饰模式的区别设计模式装饰模式桥接模式和装饰模式都是通过将继承关系转换为关联关系从而减少系统中类的数量,降低系统的耦合性。装饰器模式支持多层装饰,通过不同的组合可以实现不同的行为。 产生桥接模式的动机: 假设这样一种情况:我们有大中小型号的毛笔,有红蓝黑三种颜料。如果需要不同颜色,不同型号的毛笔有如下两种设计方法: 为每一种型号的毛笔都提供三种颜料的版本。 将毛笔和颜料分开...

    quietin 评论0 收藏0
  • 焖面&amp;适配器

    摘要:门面模式焖面有两个作用一是简化类的接口二是消除类与使用他的业务代码之间的耦合他几乎是所有库的核心原则通过建立一些便利方法可以让复杂系统变得更加简单易用焖面模式可以使库提供的工具更加容易理解焖面可以简化错误记录或者跟踪页面视图统计数据这类这类 门面模式 焖面,有两个作用,一是简化类的接口;二是消除类与使用他的业务代码之间的耦合.他几乎是所有 JS 库的核心原则.通过建立一些便利方法可以让...

    suemi 评论0 收藏0
  • 设计模式之桥接模式

    摘要:桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系组合或者聚合关系而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。 0x01.定义与类型 定义:将抽象部分与它的具体实现部分分离,使它们都可以独立地变化。 桥接模式将继承关系转化成关联关系,它降低了类与类之间的耦合度,减少了系统中类的数量,也减少了代码量。 桥接模式中的所谓脱耦,就是指在一个软...

    kycool 评论0 收藏0
  • 设计模式-02-桥接模式

    摘要:桥接模式概述桥接模式将抽象部分与它的实现部分分离,使他们都可以独立地变化。实现使用发送信息的例子来实现桥接模式。桥接模式也从侧面体现了使用对象组合的方式比继承要来得更灵活。代码实现桥接模式 桥接模式 概述 桥接模式将抽象部分与它的实现部分分离,使他们都可以独立地变化。通俗地说,桥接就是在不同的东西之间搭一个桥,让它们能够连接起来,可以相互通讯和使用。在桥接模式中的桥接是在被分离的抽象部...

    ethernet 评论0 收藏0
  • JavaScript设计模式系列六:桥接模式

    摘要:桥接模式桥接是用于把抽象化与现实化解耦,使得二者可以独立变化,这种类型的设计模式属于结构型模式,它通过提供抽象化和现实化之间的桥接结构,实现二者的解耦。所以接口和实现是可以组合的,这种组合我们称之为桥接模式。主要用在系统开始设计的时候使用。 桥接模式 桥接(Bridge)是用于把抽象化与现实化解耦,使得二者可以独立变化,这种类型的设计模式属于结构型模式,它通过提供抽象化和现实化之间的桥...

    jzzlee 评论0 收藏0

发表评论

0条评论

yuanxin

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<