十.结构型设计模式——Bridge Pattern(桥接模式)
-
定义
将抽象部分与实现部分分离,使得它们两部分可以独立地变化。
UML类图如下:
其中类和对象的关系为:
1. Abstraction(抽象类):定义抽象类的接口,维护一个Implementor(实现抽象类)的对象。
2. RefinedAbstraction(扩充抽象类):扩充由Abstraction定义的接口。
3. Implementor(实现类接口):定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般地将,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多复杂的操作。
4. ConcreteImplementor(具体实现类):实现Implementor的接口并且具体实现它。
典型应用的顺序图如下:
-
实例1——商业对象与数据对象
采用桥接模式,商业对象会将实现分离给数据对象去处理。一个商业对象采用不同的数据对象就可以实现不同的处理,而不需要由多个商业对象来实现不同的处理。类图如下:
//抽象数据对象实现类
abstract class DataObject
{
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();
}
//具体实现类——客户数据对象
class CutomersDataObject : DataObject
{
private ArrayList cusotmers = new ArrayList();
private int current = 0;
public CutomersDataObject()
{
cusotmers.Add("张三");
cusotmers.Add("李四");
cusotmers.Add("王五");
cusotmers.Add("朱六");
cusotmers.Add("赵七");
}
public override void NextRecord()
{
if (current <= cusotmers.Count - 1)
current++;
}
public override void PriorRecord()
{
if (current > 0)
current--;
}
public override void NewRecord(string name)
{
cusotmers.Add(name);
}
public override void DeleteRecord(string name)
{
cusotmers.Remove(name);
}
public override void ShowRecord()
{
Console.WriteLine(cusotmers[current]);
}
public override void ShowAllRecords()
{
foreach (string name in cusotmers)
Console.WriteLine(" " + name);
}
}
//商业对象类
class BusinessObject
{
private DataObject dataObject;
protected string group;
//构造函数
public BusinessObject(string group)
{
this.group = group;
}
//数据对象属性
public DataObject DataObject
{
set { dataObject = value; }
get { return dataObject; }
}
//调用数据对象的引用来处理
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("客户合集:{0}", group);
dataObject.ShowAllRecords();
}
}
//商业对象派生类——客户商业对象
class CustomersBusinessObject : BusinessObject
{
//构造函数,调用原接口的构造函数
public CustomersBusinessObject(string group) : base(group) { }
public override void ShowAll()
{
//在原显示所有数据的基础上增加上下分行线
Console.WriteLine();
Console.WriteLine("-----------------------------------------");
base.ShowAll();
Console.WriteLine("-----------------------------------------");
}
}
//客户应用测试
class Client
{
[STAThread]
static void Main(string[] args)
{
//创建扩充抽象类——客户商业对象
CustomersBusinessObject customers = new CustomersBusinessObject("杭州客户名单");
//设定它的具体实现类——客户数据对象
customers.DataObject = new CutomersDataObject();
//单个显示出来
customers.Show();
customers.Next();
customers.Show();
customers.Next();
customers.Show();
customers.New("陈八");
//显示其所有数据
customers.ShowAll();
Console.Read();
}
}
-
实例2——不同系统的图像处理
假定我们开发一个图像观察软件,可以在Windows操作系统中查看BMP类型的文件,同时我们又扩展它的功能,使其可以查看其他的图像格式,如Gif等,我们设计的类图如下:
Image将会设计成抽象类或接口,GifImage,BMPImage是具体的派生类。我们现在修改类结构,使得我们可以在其他系统中使用这个Image类。采用前面的设计模式很难实现这一功能,如果我们采用桥接模式,将实现与抽象分开,就可以做到这一点。类图如下:
//先定义图像实现类的接口
public interface IImageImp
{
void DoPaint(string str);
}
//定义在Windows下的图像实现类,实现IImageImp接口
public class WinImp : IImageImp
{
public void DoPaint(string str)
{
Console.WriteLine(str + " at WinOS");
}
}
//定义在Unix下的图像实现类,实现IImageImp接口
public class UnixImp : IImageImp
{
public void DoPaint(string str)
{
Console.WriteLine(str + " at UnixOS");
}
}
//定义抽象类Image,包含IImageImp的引用
public abstract class Image
{
protected IImageImp iImptouse;
public void SetImageImp(IImageImp ip)
{
this.iImptouse = ip;
}
public abstract void Method(string str);
}
//派生的BMPImage是Image的子类,它重写Method方法,调用了内部的IImageImp引用
public class BMPImage : Image
{
public override void Method(string str)
{
string s1 = str + "\nBMP Image";
this.iImptouse.DoPaint(s1);
}
}
//派生的GifImage是Image的子类,它重写Method方法,调用了内部的IImageImp引用
public class GifImage : Image
{
public override void Method(string str)
{
string s1 = str + "\nGif Image";
this.iImptouse.DoPaint(s1);
}
}
//客户应用测试
class Client
{
[STAThread]
static void Main(string[] args)
{
Image image = new BMPImage();
image.SetImageImp(new WinImp());
image.Method("BMP begin to pain");
image.SetImageImp(new UnixImp());
image.Method("BMP begin to pain");
image = new GifImage();
image.SetImageImp(new WinImp());
image.Method("Gif begin to pain");
image.SetImageImp(new UnixImp());
image.Method("Gif begin to pain");
Console.Read();
}
}
-
优势和缺陷
桥接模式可以从接口中分离实现功能,使得设计更具扩展性,这样,客户调用方法时根本不需要知道实现的细节。
桥接模式减少了子类,假设程序要在2个操作系统中处理6种图像格式,纯粹的继承就需要(2*6)12个子类,而应用桥接模式,只需要(2+6)8个子类。它使得代码更清洁,生成的执行程序文件更小。
桥接模式的缺陷是抽象类与实现类的双向连接使得运行速度减慢。
-
应用情景
下面的情景很适合应用桥接模式:
1. 你想避免抽象方法和其实现方法捆定在一起。
2. 抽象接口和它的实现都需要扩展出子类以备使用。
3. 变动实现的方法根本不会影响客户程序调用部分(甚至不用重新编译)。