登录
注册
搜索
帮助
会员
界面
简洁版本
在线
bugsbox技术论坛
软件开发技术
设计模式
设计模式(16)-Bridge Pattern
帖子标题
BugsBox原创
BugsBox C#代码生成器
电脑问题
联想Y430,450,G430闪屏信息收集
软件问题
综合问题
算法学习
数据结构
正则表达式
编程语言
F#
C#
VB.NET
JAVA
JavaScript
HTML/DHTML/XHTML
C&C++
软件开发技术
JSP
JSF
Asp.net
J2ME
Windows Form
Asp
Web 2.0
JavaScript高级技术
文档技术
建模工具
设计模式
php
数据库
Sql Server
Access
Oracle
MySql
其他交流
论坛意见
闲聊人生
无厘灌水
1
/ 1 页
1
跳转
页
查看:
324
设计模式(16)-Bridge Pattern
hutaoshu
hutaoshu
组别:
管理员
性别:
来自:
积分:
197
帖子:
192
注册:
2009-03-31
2009-07-16 11:28
|
只看楼主
树型
|
收藏
|
小
中
大
1
设计模式(16)-Bridge Pattern
一、 桥梁(Bridge)模式桥梁模式是一个非常有用的模式,也是比较复杂的一个模式。熟悉这个模式对于理解面向对象的设计原则,包括"开-闭"原则(OCP)以及组合/聚合复用原则(CARP)都很有帮助。理解好这两个原则,有助于形成正确的设计思想和培养良好的设计风格。
注:《Java与模式》一书认为Bridge模式不是一个使用频率很高的模式,我不太赞同,我认为Bridge模式中蕴涵了很多设计模式的关键思想在里面,所以我这里采纳了《Design Patterns Explained》一书的作者Alan Shalloway与James R. Trott的观点:The Bridge pattern is quite a bit more complex than the other patterns you just learned; it is also much more useful.
桥梁模式的用意
【GOF95】在提出桥梁模式的时候指出,桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。这句话有三个关键词,也就是抽象化、实现化和脱耦。
抽象化
存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当做同样的实体对待【LISKOV94】。
实现化
抽象化给出的具体实现,就是实现化。
脱耦
所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。
将两个角色之间的继承关系改为聚合关系,就是将它们之间的强关联改换成为弱关联。因此,桥梁模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。这就是桥梁模式的用意。
二、 桥梁模式的结构桥梁模式【GOF95】是对象的结构模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
下图所示就是一个实现了桥梁模式的示意性系统的结构图。
可以看出,这个系统含有两个等级结构,也就是:
由抽象化角色和修正抽象化角色组成的抽象化等级结构。
由实现化角色和两个具体实现化角色所组成的实现化等级结构。
桥梁模式所涉及的角色有:
抽象化(Abstraction)角色:
抽象化给出的定义,并保存一个对实现化对象的引用。
修正抽象化(Refined Abstraction)角色:
扩展抽象化角色,改变和修正父类对抽象化的定义。
实现化(Implementor)角色:
这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
具体实现化(Concrete Implementor)角色:
这个角色给出实现化角色接口的具体实现。
三、 桥梁模式的示意性源代码
//
Bridge pattern -- Structural example
using
System;
//
"Abstraction"
class
Abstraction
{
//
Fields
protected
Implementor implementor;
//
Properties
public
Implementor Implementor
{
set
{ implementor
=
value; }
}
//
Methods
virtual
public
void
Operation()
{
implementor.Operation();
}
}
//
"Implementor"
abstract
class
Implementor
{
//
Methods
abstract
public
void
Operation();
}
//
"RefinedAbstraction"
class
RefinedAbstraction : Abstraction
{
//
Methods
override
public
void
Operation()
{
implementor.Operation();
}
}
//
"ConcreteImplementorA"
class
ConcreteImplementorA : Implementor
{
//
Methods
override
public
void
Operation()
{
Console.WriteLine(
"
ConcreteImplementorA Operation
"
);
}
}
//
"ConcreteImplementorB"
class
ConcreteImplementorB : Implementor
{
//
Methods
override
public
void
Operation()
{
Console.WriteLine(
"
ConcreteImplementorB Operation
"
);
}
}
/**/
///
<summary>
///
Client test
///
</summary>
public
class
Client
{
public
static
void
Main(
string
[] args )
{
Abstraction abstraction
=
new
RefinedAbstraction();
//
Set implementation and call
abstraction.Implementor
=
new
ConcreteImplementorA();
abstraction.Operation();
//
Change implemention and call
abstraction.Implementor
=
new
ConcreteImplementorB();
abstraction.Operation();
}
}
四、 调制解调器问题感觉《敏捷软件开发-原则、模式与实践》中关于Bridge模式的例子很好。(《Java与模式》一书33章的对变化的封装一节也写得很不错,推荐大家读一读。它深入的阐述了《Design Patterns Explained》一书中"1)Design to interfaces. 2)Favor composition over inheritance. 3)Find what varies and encapsulate it"的三个观点。)。
如图所示,有大量的调制解调器客户程序在使用Modem接口。Modem接口被几个派生类HayesModem、USRoboticsModem和EarniesModem实现。它很好地遵循了OCP、LSP和DIP。当增加新种类的调制解调器时,调制解调器的客户程序不会受影响。
假定这种情形持续了几年,并有许多调制解调器的客户程序都在使用着Modem接口。现出现了一种不拨号的调制解调器,被称为专用调制解调器。它们位于一条专用连接的两端。有几个新应用程序使用这些专用调制解调器,它们无需拨号。我们称这些使用者为DedUser。但是,客户希望当前所有的调制解调器客户程序都可以使用这些专用调制解调器。他们不希望去更改许许多多的调制解调器客户应用程序,所以完全可以让这些调制解调器客户程序去拨一些假(dummy)电话号码。
如果能选择的话,我们会把系统的设计更改为下图所示的那样。
我们把拨号和通信功能分离为两个不同的接口。原来的调制解调器实现这两个接口,而调制解调器客户程序使用这两个接口。DedUser只使用Modem接口,而DedicateModem只实现Modem接口。但这样做会要求我们更改所有的调制解调器客户程序--这是客户不允许的。
一个可能的解决方案是让DedicatedModem从Modem派生并且把dial方法和hangup方法实现为空,就像下面这样:
几个月后,已经有了大量的DedUser,此时客户提出了一个新的更改。为了能拨国际电话号码、信用卡电话、PIN标识电话等等,必修对现有dial中使用char[10]存储号码改为能够拨打任意长度的电话号码。
显然,所有的调制解调器客户程序都必须更改。客户同意了对调制解调器客户程序的更改,因为他们别无选择。糟糕的是,现在必须要去告诉DedUser的编写者,他们必须要更改他们的代码!你可以想象他们听到这个会有多高兴。本来他们是不用调用dial的。
这就是许多项目都会具有的那种有害的混乱依赖关系。系统某一部分中的一个杂凑体(kludge)创建了一个有害的依赖关系,最终导致系统中完全无关的部分出现问题。
如果使用ADAPTER模式解决最初的问题的话,就可以避免这个严重问题。如图:
请注意,杂凑体仍然存在。适配器仍然要模拟连接状态。然而,所有的依赖关系都是从适配器发起的。杂凑体和系统隔离,藏身于几乎无人知晓的适配器中。
BRIDGE模式
看待这个问题,还有另外一个方式。现在,出现了另外一种切分Modem层次结构的方式。如下图:
这不是一个理想的结构。每当增加一款新硬件时,就必须创建两个新类--一个针对专用的情况,一个针对拨号的情况。每当增加一种新连接类型时,就必须创建3个新类,分别对应3款不同的硬件。如果这两个自由度根本就是不稳定的,那么不用多久,就会出现大量的派生类。
在类型层次结构具有多个自由度的情况中,BRIDGE模式通常是有用的。我们可以把这些层次结构分开并通过桥把它们结合到一起,而不是把它们合并起来。如图:
我们把调制解调器类层次结构分成两个层次结构。一个表示连接方法,另一个表示硬件。
这个结构虽然复杂,但是很有趣。它的创建不会影响到调制解调器的使用者,并且还完全分离了连接策略和硬件实现。ModemConnectController的每个派生类代表了一个新的连接策略。在这个策略的实现中可以使用sendlmp、receivelmp、diallmp和hanglmp。新imp方法的增加不会影响到使用者。可以使用ISP来给连接控制类增加新的接口。这种做法可以创建出一条迁移路径,调制解调器的客户程序可以沿着这条路径慢慢地得到一个比dial和hangup层次更高的API。
五、 另外一个实际应用Bridge模式的例子该例子演示了业务对象(BusinessObject)通过Bridge模式与数据对象(DataObject)解耦。数据对象的实现可以在不改变客户端代码的情况下动态进行更换。
//
Bridge pattern -- Real World example
using
System;
using
System.Collections;
//
"Abstraction"
class
BusinessObject
{
//
Fields
private
DataObject dataObject;
protected
string
group;
//
Constructors
public
BusinessObject(
string
group )
{
this
.group
=
group;
}
//
Properties
public
DataObject DataObject
{
set
{ dataObject
=
value; }
get
{
return
dataObject; }
}
//
Methods
virtual
public
void
Next()
{ dataObject.NextRecord(); }
virtual
public
void
Prior()
{ dataObject.PriorRecord(); }
virtual
public
void
New(
string
name )
{ dataObject.NewRecord( name ); }
virtual
public
void
Delete(
string
name )
{ dataObject.DeleteRecord( name ); }
virtual
public
void
Show()
{ dataObject.ShowRecord(); }
virtual
public
void
ShowAll()
{
Console.WriteLine(
"
Customer Group: {0}
"
, group );
dataObject.ShowAllRecords();
}
}
//
"RefinedAbstraction"
class
CustomersBusinessObject : BusinessObject
{
//
Constructors
public
CustomersBusinessObject(
string
group )
:
base
( group )
{}
//
Methods
override
public
void
ShowAll()
{
//
Add separator lines
Console.WriteLine();
Console.WriteLine(
"
------------------------
"
);
base
.ShowAll();
Console.WriteLine(
"
------------------------
"
);
}
}
//
"Implementor"
abstract
class
DataObject
{
//
Methods
abstract
public
void
NextRecord();
abstract
public
void
PriorRecord();
abstract
public
void
NewRecord(
string
name );
abstract
public
void
DeleteRecord(
string
name );
abstract
public
void
ShowRecord();
abstract
public
void
ShowAllRecords();
}
//
"ConcreteImplementor"
class
CustomersDataObject : DataObject
{
//
Fields
private
ArrayList customers
=
new
ArrayList();
private
int
current
=
0
;
//
Constructors
public
CustomersDataObject()
{
//
Loaded from a database
customers.Add(
"
Jim Jones
"
);
customers.Add(
"
Samual Jackson
"
);
customers.Add(
"
Allen Good
"
);
customers.Add(
"
Ann Stills
"
);
customers.Add(
"
Lisa Giolani
"
);
}
//
Methods
public
override
void
NextRecord()
{
if
( current
<=
customers.Count
-
1
)
current
++
;
}
public
override
void
PriorRecord()
{
if
( current
>
0
)
current
--
;
}
public
override
void
NewRecord(
string
name )
{
customers.Add( name );
}
public
override
void
DeleteRecord(
string
name )
{
customers.Remove( name );
}
public
override
void
ShowRecord()
{
Console.WriteLine( customers[ current ] );
}
public
override
void
ShowAllRecords()
{
foreach
(
string
name
in
customers )
Console.WriteLine(
"
"
+
name );
}
}
/**/
///
<summary>
///
Client test
///
</summary>
public
class
BusinessApp
{
public
static
void
Main(
string
[] args )
{
//
Create RefinedAbstraction
CustomersBusinessObject customers
=
new
CustomersBusinessObject(
"
Chicago
"
);
//
Set ConcreteImplementor
customers.DataObject
=
new
CustomersDataObject();
//
Exercise the bridge
customers.Show();
customers.Next();
customers.Show();
customers.Next();
customers.Show();
customers.New(
"
Henry Velasquez
"
);
customers.ShowAll();
}
}
六、 在什么情况下应当使用桥梁模式根据上面的分析,在以下的情况下应当使用桥梁模式:
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。
设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。
一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。
虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
参考文献:
阎宏,《Java与模式》,电子工业出版社
[美]James W. Cooper,《C#设计模式》,电子工业出版社
[美]Alan Shalloway James R. Trott,《Design Patterns Explained》,中国电力出版社
[美]Robert C. Martin,《敏捷软件开发-原则、模式与实践》,清华大学出版社
[美]Don Box, Chris Sells,《.NET本质论 第1卷:公共语言运行库》,中国电力出版社
发送短消息
查看公共资料
查找该会员全部帖子
UID:
3
精华:
1
威望:
0
金钱:
40.05 元
状态:
离线
<<
上一主题
|
下一主题
>>
1
/ 1 页
1
跳转
页
论坛跳转...
BugsBox原创
BugsBox C#代码生成器
电脑问题
联想Y430,450,G430闪屏信息收集
软件问题
综合问题
算法学习
数据结构
正则表达式
编程语言
F#
F#语言基础
F#编程指南
C#
C# 参考
C# 编程指南
Linq专题
VB.NET
JAVA
JavaScript
HTML/DHTML/XHTML
C&C++
软件开发技术
JSP
SSH
JSF
sun-ri
myfaces
icefaces
richfaces
seam
Asp.net
asp.net控件开发
J2ME
Windows Form
WinForm控件开发
Asp
Web 2.0
JavaScript高级技术
Ajax
ext
jquery
prototype
文档技术
建模工具
设计模式
php
数据库
Sql Server
Access
Oracle
MySql
其他交流
论坛意见
闲聊人生
无厘灌水
我的主题
我的帖子
我的精华
帖子标题
空间日志
相册标题
作 者