1

桥接模式

将抽象部分与实现部分解耦,使两者互不影响,可以独立变化。在桥接模式中,将两个独立变化的维度设计成两个独立继承的结构,而不是将二者耦合在一起形成多层继承结构。桥接模式在抽象层建立起一个抽象关联,类似连接两个继承结构的桥梁,所以叫桥接模式。

桥接模式是一种对象结构型模式,用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代传统的多层继承,将类与类之间的静态继承转换为动态组合关系,使得系统更加灵活。

桥接模式的结构

image.png

  • AbstractClass(抽象类):定义接口的抽象类,并关联了一个Implementor类型的对象。
  • RefinedAbstraction(扩展抽象类):一般是一个具体类,实现了抽象类中定义的抽象业务方法,并可以调用Implementor中定义的业务方法。
  • Implementor(实现接口类):通常只声明基本操作,其实现交由具体的子类。
  • SpecificImplementor(具体实现类):实现Implementor接口,不同的具体实现类中提供不同的业务方法实现。

桥接模式的实现

使用桥接模式时,应先识别出一个类所具有的两个变化维度,将它们设计为两个独立的继承结构,并为两个维度都提供抽象层,且建立抽象耦合。通常将具有两个独立变化维度的类的一些普通业务方法和与之关系最密切的维度设计为抽象类层次,将另一个维度设计成实现类层次。

如:毛笔拥有型号(大小)和颜色两个变化维度,分析得毛笔的型号时其固有的维度,因此可以设计一个抽象的毛笔类,在类中声明毛笔的业务方法,将各种型号的毛笔作为其子类。颜色是毛笔的另一个维度,与其存在“设置”(为毛笔设置颜色)的关系,因此可以提供一个抽象的颜色接口,将具体的颜色作为实现该接口的子类。这里型号是毛笔的抽象部分,颜色是毛笔的实现部分。

场景
开发一个数据转换工具,可以将数据库中的数据转换成多种文件格式,如:TXT、XML、PDF等,同时该工具需要支持多种不同的数据库,使用桥接模式设计,代码如下:

//数据库数据
class DBData { }

interface IDBImpl
{
    DBData ParseData();
}

class MySqlDb : IDBImpl
{
    public DBData ParseData()
    {
        //解析MySql数据库据...
        Console.Write("解析MySql数据:");
        return new DBData();
    }
}

class OracleDb : IDBImpl
{
    public DBData ParseData()
    {
        //解析Oracle数据库据...
        Console.Write("解析Oracle数据:");
        return new DBData();
    }
}

abstract class File
{
    protected IDBImpl m_Impl;

    public void SetDBImpl(IDBImpl impl)
    {
        this.m_Impl = impl;
    }

    public abstract void OutputFile(string outputFile);
}

class TxtFile : File
{
    public override void OutputFile(string outputFile)
    {
        //获取数据库数据
        var data = this.m_Impl.ParseData();
        //将数据库数据data转换成Txt文件...
        Console.WriteLine("将数据转换成: {0}.txt", outputFile);
    }
}

class XmlFile : File
{
    public override void OutputFile(string outputFile)
    {
        //获取数据库数据
        var data = this.m_Impl.ParseData();
        //将数据库数据data转换成Xml文件...
        Console.WriteLine("将数据转换成: {0}.xml", outputFile);
    }
}

class PdfFile : File
{
    public override void OutputFile(string outputFile)
    {
        //获取数据库数据
        var data = this.m_Impl.ParseData();
        //将数据库数据data转换成Pdf文件...
        Console.WriteLine("将数据转换成: {0}.Pdf", outputFile);
    }
}

使用

//配置文件
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <appSettings>
        <add key="file" value="BridgeSample.TxtFile"/>
        <add key="db" value="BridgeSample.MySqlDb"/>
    </appSettings>
</configuration>

static void Main()
{
    //读取配置文件
    var fileTypeCfg = ConfigurationManager.AppSettings["file"];
    var dbTypeCfg = ConfigurationManager.AppSettings["db"];

    //反射创建对象
    var fileType = Type.GetType(fileTypeCfg);
    var file = (File)Activator.CreateInstance(fileType);
    var dbType = Type.GetType(dbTypeCfg);
    var db = (IDBImpl)Activator.CreateInstance(dbType);

    file.SetDBImpl(db);
    file.OutputFile("桥接模式");

    Console.ReadKey();
}

运行结果

image.png

如果需要更换文件格式或者更换数据库,只需修改配置文件即可,例如将配置文件改为

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <appSettings>
        <add key="file" value="BridgeSample.PdfFile"/>
        <add key="db" value="BridgeSample.OracleDb"/>
    </appSettings>
</configuration>

再次运行程序,输出结果如下

image.png

如果出现新的数据库或者需要增加新的文件格式支持时,只需要扩展一个对应的扩展抽象类或者一个具体实现类即可,无需修改系统的原有代码。

桥接模式的优点

  • 分离抽象接口和实现部分,让抽象和实现可以沿着各自的维度进行变化且互不影响。并且可以任意组合子类获得多维度组合对象。
  • 提高了系统的可扩展性,在不同的变化维度中任意扩展一个维度,不需要修改原有代码,符合开闭原则。

桥接模式的不足

  • 增加系统的理解难度与复杂度。由于关联关系建立在抽象层,所以要求开发者一开始就设计好抽象层,并针对抽象层进行设计编程。
  • 桥接模式要求能够正确识别出系统中两个独立变化的维度,因此使用范围受到一定的限制,同时如何正确识别独立维度也需要一定的经验积累,对开发者有更高的要求。

桥接模式应用场景

  • 系统需要在抽象化和具体化之间增加灵活性,避免两个层次之间建立静态的继承关系,使用桥接模式可以建立一个抽象层之间的关联关系。
  • 抽象部分和实现部分可以通过继承的方式独立扩展而互不影响,在程序运行时可以将一个抽象化子类和实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  • 一个类存在一个以上独立变化的维度,且这些维度都需要独立地进行扩展。
  • 不希望使用继承模式,或者不希望因为多层继承导致类的个数急剧增加,可以使用桥接模式。

DoubleJ
7 声望3 粉丝

« 上一篇
适配器模式
下一篇 »
组合模式