几个月后,已经有了大量的DedUser,此时客户提出了一个新的更改。为了能拨国际电话号码、信用卡电话、PIN标识电话等等,必修对现有dial中使用char[10]存储号码改为能够拨打任意长度的电话号码。
显然,所有的调制解调器客户程序都必须更改。客户同意了对调制解调器客户程序的更改,因为他们别无选择。糟糕的是,现在必须要去告诉DedUser的编写者,他们必须要更改他们的代码!你可以想象他们听到这个会有多高兴。本来他们是不用调用dial的。
这就是许多项目都会具有的那种有害的混乱依赖关系。系统某一部分中的一个杂凑体(kludge)创建了一个有害的依赖关系,最终导致系统中完全无关的部分出现问题。
如果使用ADAPTER模式解决最初的问题的话,就可以避免这个严重问题。如图:
BRIDGE模式
看待这个问题,还有另外一个方式。现在,出现了另外一种切分Modem层次结构的方式。如下图:
这不是一个理想的结构。每当增加一款新硬件时,就必须创建两个新类--一个针对专用的情况,一个针对拨号的情况。每当增加一种新连接类型时,就必须创建3个新类,分别对应3款不同的硬件。如果这两个自由度根本就是不稳定的,那么不用多久,就会出现大量的派生类。
在类型层次结构具有多个自由度的情况中,BRIDGE模式通常是有用的。我们可以把这些层次结构分开并通过桥把它们结合到一起,而不是把它们合并起来。如图:
我们把调制解调器类层次结构分成两个层次结构。一个表示连接方法,另一个表示硬件。
这个结构虽然复杂,但是很有趣。它的创建不会影响到调制解调器的使用者,并且还完全分离了连接策略和硬件实现。ModemConnectController的每个派生类代表了一个新的连接策略。在这个策略的实现中可以使用sendlmp、receivelmp、diallmp和hanglmp。新imp方法的增加不会影响到使用者。可以使用ISP来给连接控制类增加新的接口。这种做法可以创建出一条迁移路径,调制解调器的客户程序可以沿着这条路径慢慢地得到一个比dial和hangup层次更高的API。
5. 另外一个实际应用Bridge模式的例子
该例子演示了业务对象(BusinessObject)通过Bridge模式与数据对象(DataObject)解耦。数据对象的实现可以在不改变客户端代码的情况下动态进行更换。
// Bridge pattern -- Real World example
using System;
using System.Collections;
// "Abstraction"
classBusinessObject
{
// Fields
privateDataObject dataObject;
protectedstring group;
// Constructors
publicBusinessObject( string group )
{
this.group = group;
}
// Properties
publicDataObject DataObject
{
set { dataObject = value; }
get {returndataObject; }
}
// Methods
virtualpublicvoidNext()
{ dataObject.NextRecord(); }
virtualpublicvoidPrior()
{ dataObject.PriorRecord(); }
virtualpublicvoidNew( string name )
{ dataObject.NewRecord( name ); }
virtualpublicvoidDelete( string name )
{ dataObject.DeleteRecord( name ); }
virtualpublicvoidShow()
{ dataObject.ShowRecord(); }
virtualpublicvoidShowAll()
{
Console.WriteLine(" Customer Group: {0} ", group );
dataObject.ShowAllRecords();
}
}
// "RefinedAbstraction"
classCustomersBusinessObject : BusinessObject
{
// Constructors
publicCustomersBusinessObject( string group )
: base ( group ) {}
// Methods
overridepublicvoidShowAll()
{
// Add separator lines
Console.WriteLine();
Console.WriteLine(" ------------------------ ");
base .ShowAll();
Console.WriteLine(" ------------------------ ");
}
}
// "Implementor"
abstractclassDataObject
{
// Methods
abstractpublicvoidNextRecord();
abstractpublicvoidPriorRecord();
abstractpublicvoidNewRecord( string name );
abstractpublicvoidDeleteRecord( string name );
abstractpublicvoidShowRecord();
abstractpublicvoidShowAllRecords();
}
// "ConcreteImplementor"
classCustomersDataObject : DataObject
{
// Fields
privateArrayList customers =newArrayList();
privateintcurrent =0;
// Constructors
publicCustomersDataObject()
{
// 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
publicoverridevoidNextRecord()
{
if( current <= customers.Count -1)
current ++ ;
}
publicoverridevoidPriorRecord()
{
if( current >0)
current -- ;
}
publicoverridevoidNewRecord( string name )
{
customers.Add( name );
}
publicoverridevoidDeleteRecord( string name )
{
customers.Remove( name );
}
publicoverridevoidShowRecord()
{
Console.WriteLine( customers[ current ] );
}
publicoverridevoidShowAllRecords()
{
foreach ( string name in customers )
Console.WriteLine(" "+ name );
}
}
/**
/// Client test
///
publicclassBusinessApp
{
publicstaticvoidMain( string [] args )
{
// Create RefinedAbstraction
CustomersBusinessObject customers =
newCustomersBusinessObject(" Chicago ");
// Set ConcreteImplementor
customers.DataObject =newCustomersDataObject();
// Exercise the bridge
customers.Show();
customers.Next();
customers.Show();
customers.Next();
customers.Show();
customers.New(" Henry Velasquez ");
customers.ShowAll();
}
}
6. 在什么情况下应当使用桥梁模式
根据上面的分析,在以下的情况下应当使用桥梁模式:
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。
设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。
一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。
虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。