[设计模式] javascript 之 桥接模式
桥接模式说明
定义:分离抽象化与实现化,使之可以自由独立的变化;
说明:由于软件环境需求原因,使得类型抽象具有多种实现以自身变化定义等情况,这使得我们要分离抽象实现与具体实现,使得抽象化与实现化解耦,使之可以分开独立的变化,使得两者可以自由添加各自处理过程实现。
桥接模式就可以解决上面的问题,桥接模式的角色:
1. 抽象化角色,这个抽象化类中定义对 实现化接口定义的引用;
2. 修正抽象化角色,这个角色扩展并修改抽象化接口定义的实现,该修改用于引用实现化接口的实现的 (底层) 操作;
3. 实现化角色,定义实现化的接口定义;
4. 具体实现化,用于对于实现化接口定义的具体实现,它是一个底层实现;
从上面可以看出,桥接模式有两个部分:抽象部分 以及 实现部分; 实现部分用于具体的底层实现,抽象部分用于封装具体的底层现实;两者间接口定义可以不一样;
5. 继承
什么是继承?
继承就是一种类与类之间的关系,或是同种类型对象之间的一种关系,使用继承后,继承的类可以拥有被继承的类所有的非私有成员或方法;
继承是面向对象最重要的一种特性,是复用的一种重要方式.
继承的优点,是可以实现代码复用,减少代码开发量,缩短开发时间.
继承的缺点,破坏了封装性,基类可能 “白箱” 暴露了方法的细节;
对象的封装,是对软件组件能成为一个良好模块性的基础,对象封装一般包括属性状态跟方法行为组成,是对象世界的整体抽象,对象封装里,哪些是内部自己用的,哪些是供外部使用调用的,都可以完美的定义。
封装性保证了对象减少与外部组件依赖,减少因为内部分改变而对其他对象造的影响。
对象的良好的封装,可以保证对象间的“高内聚,低耦合”;
对于继承复用来说,类编译是静态共享的,父类公共方法及成员可以直接的使用。
由于两者的这种依赖关系,父类的修改,可能会影响到子类的实现,有时还得修改子类的某些操作等;反过来,子类可以重写跟扩展父类的实现,父所定义的约束,容易被轻易的被子类所改变。
//
由于两者在执行时即已存在固定的关系,所以当有新类型出现时,势必重新定义子类,而无法在另一种层面上进行复用,就是当的类型出现时,当内部实现在现有子类与父类都无法满足时,必须重新定义子类,甚至定义新的类(此可能展现的功能组成还是差不多,只是新的实现不一样了);
6. 组合与聚合;
1>. 组合,这是一种强关联,由部分组合成整体,是一种拥有的关系,具有相同的生命周期,部分脱离整将不具有意义;好比像狼群,狮子这种动态一样,身体与四肢眼睛,就是整体与部分的组合关系;
2>. 聚合,这是一种弱关联,两者只是一种包含关系,部分可以脱离整体独立的存在;就是上面的狼群或狮子一样,两者都是群居动物,单个个体是独立的存在,万一脱离了群体,还是可以自己活动。
面向对象原则的第二点,就是尽量使用组合/聚合方式代替继承复用。
组合/聚合,尽量少使用继承,可以使对象尽量只负责一项工作,在面对接口编程思想下,使用组合/聚合复用方法,因为聚合对象采用接口实现,接口可以抽象实例化或使用依赖注入的方法,指向对象的实现,因此当需要改变实现时,只需要使接口指定需要的现实即可。(但是继承这时基本是要重建子类继承)
7. 桥接模式的实现,靠得是抽象,多实现,以及抽象对实现角色的关联,这种关联,就是聚合关系;
8. 各种关系图对比:
1>. 继承关系:
当父类跟子类为 is-A 的关系时,用继承;
2>. 组合关系
当子类为父类的一个不可分割部分时,用 组合关系, 两者是 has-A 关系;
3>. 聚合关系是空心菱;
4>. 桥接关系图;
上图表示 抽象类与实现,是聚合的关系,实现只是抽象的一种实现,由于抽象基于 interface 接口编程,所以当 有另外的实现时,只需让 interface 指向需要的实现即可;
桥接模式分两部分,一部分是事物抽象,比如一个发消息的功能,可能有邮件跟短信发送几种;一部分可以说是事物行为实现抽象,比如消息内容的性质,比如说普通的,紧急的等; 我们也可以把实现部分看成是一种通用的组件抽象;
桥接模式的思想,就是让我们独立各种抽象的实现,像上面的 邮件跟短信,两者基于同一个接口interface实现,当客户端有需求时,可以在两者之间通过接口方便的切换;
场景实例:
比如我们的教学,教学有时是在普通教室进行的,有的是在多媒体教室进行的,有的是在实验室进行;教学的内容有英语,语文,物理,数学等; 当然说,普通教室是什么都可以教的,多媒体教室也是什么课程都可以用的,而且使用起来,效果更好,学生更喜欢;
在这里,在什么地方教书,是种方式,而课程是一种内容;方式下又可以对各内容的实现;
如果只是采用继承的方式,那么每个内容的实现都得自己实现,这样会产生很多重复的代码;
这时我们可以采用桥接的模式,对方式跟内容进行各自己的实现;方式再与实现使用 聚合方式 关联起来;
源码实例
1. 定义各种课程.
教程内容抽象实现;
function Couse() { } Couse.prototype = { chinese: function() { console.log('教语文果'); }, english: function() { console.log('教英文课'); }, physics: function() { console.log('教物理课'); } }
接下来对教学方式,进行抽象:
1>. 普通教室:
function ordinary(couse) { this.couse = couse; this.where = '普通教室'; } ordinary.prototype = { chinese: function() { console.log('在 ' + this.where + ' '); this.couse.chinese(); }, english: function() { console.log('在 ' + this.where + ' '); this.couse.english(); }, physics: function() { console.log('在 ' + this.where + ' '); this.couse.physics(); } }
2>. 多媒体教室:
function multimedia(couse) { this.couse = couse; this.where = '普通教室'; } multimedia.prototype = { chinese: function() { console.log('在 ' + this.where + ' '); this.couse.chinese(); //定义新内容; console.log('接下来将播相关的课程影片') }, english: function() { console.log('在 ' + this.where + ' '); this.couse.english(); //定义新内容; console.log('请戴好耳机认真听;') }, physics: function() { console.log('在 ' + this.where + ' '); this.couse.physics(); } }
使用方法:
//普通教室 var couse = new Couse(); var classroom = ordinary(couse); classroom.chinese(); //多媒体教室 classroom = new multimeida(couse); classroom.chinese();
其他说明
桥接模式,比较难懂,主要是要理解一些设计模式的原则,以及桥接模式的思想,原则就是不要烂用 继承 因为继承是编译即定的他无法改变父的实现;面向对象设计建议使用组合/聚合复用方式来代替继承复用,以及面向接口编程的好处;理解组合聚合复用给对象类封装带来的好处;
桥接模式要求对象抽象与实现抽象分开处理,这样易于扩展,避免内部修改,体现了面向对象对扩展开放,对修改关闭的原则。这种模式虽然不常遇见但非常有用。