桥接(Bridge)模式很像适配器模式,两者都是使用类把一种接口转换为另一种接口;然而,适配器的意图是使得一个或者多个类的接口与某个特定类的接口看起来一样,而桥接模式的设计目标是分离类的接口和实现,这样就可以变更或是更换实现而无需修改客户端的代码。这也就是平时我们说的非常多的面向对象编程可以把接口和实现分离开来,然而大部分的功能还是要通过类的多态性来实现,以及通过接口访问实现类的成员。
桥接模式中包括:
1. 抽象,其定义了类的接口;
2. 精化抽象,其扩展和实现接口;
3. 实现者,其定义实现类的接口;
4. 具体实现者,其为实现类。
我们有这样的一个程序,左右两边分别显示一个列表栏,左侧显示的是产品名称,右侧的DataGrid中需要显示左侧产品列表中销售出去的产品的名称和数量。适配器模式有它的局限性,我们这里需要根据产品数据生成两种类型的显示,一种在左侧的列表框中,一种在右侧的数据表格中。
用户界面如图:
这里我们左侧和右侧用于显示数据的组件不一样,所以需要一个中间的桥梁来连接两个显示。
先定义一个接口,该接口保留了类的相同部分,而不考虑实际实现类的类型和复杂性。
Bridger接口的定义如下:
using System;
using System.Collections ;
namespace BasicBridge
{
///
/// Summary description for Bridger.
///
//Bridge interface to display list classes
public interface Bridger {
void addData(ArrayList col);
}
}
这个接口中只规定了一个addData()的函数,函数的参数为一个ArrayList的对象引用。为我们将来要进行的控件添加数据。
我们还定义了一个Product类,包含一些基本数据:
using System;
namespace BasicBridge
{
///
/// Summary description for Product.
///
public class Product : IComparable {
private string quantity;
private string name;
//-----
public Product(string line) {
int i = line.IndexOf ("--");
name =line.Substring (0, i).Trim ();
quantity = line.Substring (i+2).Trim ();
}
//-----
public string getQuantity() {
return quantity;
}
//-----
public string getName() {
return name;
}
//-----
public int CompareTo(object p) {
Product prod =(Product) p;
return name.CompareTo (prod.getName ());
}
}
}
桥接的另一端就是实现类,这些类通常有着详细的更低层面的接口。接口定义如下:
using System;
namespace BasicBridge
{
///
/// Summary description for VisList.
///
public interface VisList {
//add a line to the display
void addLine(Product p);
//remove a line from the display
void removeLine(int num);
}
}
这个接口给具体的实现类规定了两个常用的操作:添加一行和删除一行。
连接左侧接口和右侧的实现类之间的桥梁就是ListBridge类,该类实例化了列表显示类的两者之一,并且是通过这两个类的共同接口来实例化的。类的具体代码如下:
using System;
using System.Collections ;
namespace BasicBridge
{
///
/// Summary description for ListBridge.
///
public class ListBridge : Bridger {
private VisList vis;
//------
public ListBridge(VisList v) {
vis = v;
}
//-----
public void addData(ArrayList ar) {
for(int i=0; i< ar.Count ; i++) {
Product p = (Product)ar[i];
vis.addLine (p);
}
}
}
}
类的关系图如图所示:
两个显示类的具体实现分别为:
using System;
using System.Windows.Forms;
namespace BasicBridge
{
///
/// Summary description for ProductList.
///
//A VisList class for the ListBox
public class ProductList : VisList {
private ListBox list;
//-----
public ProductList(ListBox lst) {
list = lst;
}
//-----
public void addLine(Product p) {
list.Items.Add (p.getName() );
}
//-----
public void removeLine(int num) {
if(num >=0 && num < list.Items.Count ){
list.Items.Remove (num);
}
}
}
}
在这个类中实现了visList接口,这样可以通过接口的调用添加数据。在桥接器中可以包含一个接口的实例。
using System;
using System.Windows.Forms;
using System.Data;
namespace BasicBridge
{
///
/// Summary description for GridList.
///
public class GridList:VisList {
private DataGrid grid;
private DataTable dtable;
private GridAdapter gAdapter;
//-----
public GridList(DataGrid grd) {
grid = grd;
dtable = new DataTable("Products");
DataColumn column = new DataColumn("ProdName");
dtable.Columns.Add(column);
column = new DataColumn("Qty");
dtable.Columns.Add(column);
grid.DataSource = dtable;
gAdapter = new GridAdapter (grid);
}
//-----
public void addLine(Product p) {
gAdapter.Add (p);
}
//-----
public void removeLine(int num) {
}
}
}
这个类也实现了VisList接口,通过桥接器的接口也可以添加数据,注意这里的添加数据是通过这个DataGrid的适配器来实现的,适配器的试下如下:
using System;
using System.Windows.Forms ;
using System.Data;
namespace BasicBridge
{
///
/// Summary description for GridAdapter.
///
public class GridAdapter {
private DataGrid grid;
private DataTable dTable;
private int row;
//-----
public GridAdapter(DataGrid grd) {
grid = grd;
dTable = (DataTable)grid.DataSource;
grid.MouseDown +=new System.Windows.Forms.MouseEventHandler (Grid_Click);
row = -1;
}
//-----
public void Grid_Click(object sender, System.Windows.Forms.MouseEventArgs e) {
DataGrid.HitTestInfo hti = grid.HitTest (e.X, e.Y);
if(hti.Type == DataGrid.HitTestType.Cell ){
row = hti.Row ;
}
}
//-----
public void Add(Product p) {
DataRow row = dTable.NewRow();
row[0] = p.getName ();
row[1] = p.getQuantity ();
dTable.Rows.Add(row);
dTable.AcceptChanges();
}
//-----
public int SelectedIndex() {
return row;
}
//-----
public void Clear() {
int count = dTable.Rows.Count ;
for(int i=0; i< count; i++) {
dTable.Rows[i].Delete ();
}
}
//-----
public void clearSelection() {}
}
}
然后就可以在主程序中通过桥接器进行对两个显示控件进行操作了:
private ArrayList products;
private VisList prodList, gridList;
public Form1() {
InitializeComponent();
init();
}
private void init() {
products = new ArrayList ();
readFile(products); //read in the data file
//create the product list
prodList = new ProductList (lsProd);
//Bridge to product VisList
Bridger lbr = new ListBridge (prodList);
//put the data into the product list
lbr.addData (products);
//create the grid VisList
gridList = new GridList(grdProd);
//Bridge to the grid list
Bridger gbr = new ListBridge (gridList);
//put the data into the grid display
gbr.addData (products);
}
这里,对两个不同的显示空间的操作都通过ListBrider这个桥接器来进行的,因为桥接器ListBridge中包含了一个可以指向具体实现类的一个VisList的引用,通过这个引用可以指向两个具体的实现类中的一个,因为两个具体的实现类都实现了ListBridge这个接口,所以两个具体的实现类都有需要的方法函数的定义,但是两个具体的实现类就可以根据自己的功能特性完全不同的实现自己需要的操作了。比如GridList类的添加数据的实现就通过了DataGrid的适配器来进行操作。
在上面的类关系图中,可以清楚看到接口和实现的分离。左边的Bridger类是抽象,ListBridge类是该抽象的实现;VisList接口则表述了列表类ProductList和GridList的公共接口,VisList接口定义了实现者的接口,具体实现者是ProductList和GridList。
下面介绍一个对于桥接的扩展。假设我们希望显示的数据做一些改动,比如希望暗战字母顺序显示产品信息,如果修改列表类或者表格类,那么很快就会对将来的代码维护带来很大的麻烦。我们需要做的是派生出一个类似于ListProduct的中间桥,这里添加排序功能。SortBridge的定义如下:
using System;
using System.Collections ;
namespace BasicBridge
{
///
/// Summary description for SortBridge.
///
public class SortBridge:ListBridge {
//-----
public SortBridge(VisList v):base(v){
}
//-----
public override void addData(ArrayList ar) {
int max = ar.Count ;
Product[] prod = new Product[max];
for(int i=0; i< max ; i++) {
prod[i] = (Product)ar[i];
}
for(int i=0; i < max ; i++) {
for (int j=i; j < max; j++) {
if(prod[i].CompareTo (prod[j])>0) {
Product pt = prod[i];
prod[i]= prod[j];
prod[j] = pt;
}
}
}
for(int i = 0; i< max; i++) {
vis.addLine (prod[i]);
}
}
}
}
这样就可以在无需改变实现的情况下就可以变更接口,反过来也是如此。
下面是一个通过树形图显示的例子:
using System;
using System.Windows.Forms;
using System.Data;
namespace BasicBridge
{
///
/// Summary description for GridList.
///
public class TreeList:VisList {
private TreeView tree;
private TreeAdapter gAdapter;
//-----
public TreeList(TreeView tre) {
tree = tre;
gAdapter = new TreeAdapter (tree);
}
//-----
public void addLine(Product p) {
gAdapter.Add (p);
}
//-----
public void removeLine(int num) {
}
}
}
这个类通过适配器给树中添加节点:
using System;
using System.Windows.Forms;
namespace BasicBridge
{
///
/// Summary description for TreeAdapter.
///
public class TreeAdapter {
private TreeView tree;
//------
public TreeAdapter(TreeView tr) {
tree=tr;
}
//------
public void Add(Product p) {
TreeNode nod;
//add a root node
nod = tree.Nodes.Add(p.getName());
//add a child node to it
nod.Nodes.Add(p.getQuantity ());
tree.ExpandAll ();
}
//------
public int SelectedIndex() {
return tree.SelectedNode.Index ;
}
//------
public void Clear() {
TreeNode nod;
for (int i=0; i< tree.Nodes.Count ; i++) {
nod = tree.Nodes [i];
nod.Remove ();
}
}
//------
public void clearSelection() {}
}
}
桥接模式的效果:
1. 桥接模式的目标是在保持客户端程序接口不变的同时允许修改实际上要显示或者要使用的类,这可以避免重新编译一组复杂的用户接口模块,而只要求重新编译桥接部分以及世嘉尚的最终显示类。
2. 可以分别扩展实现类和桥接类,通常情况下,这两者彼此之间无需过多的交互。
3. 更加容易向客户端影藏实现的细节。