设计模式适配器模式与桥接模式(设计模式系列桥接模式)

设计模式适配器模式与桥接模式(设计模式系列桥接模式)(1)

作者公众号:一角钱技术(org_yijiaoqian)

前言
  • 23种设计模式速记
  • 单例(singleton)模式
  • 工厂方法(factory method)模式
  • 抽象工厂(abstract factory)模式
  • 建造者/构建器(builder)模式
  • 原型(prototype)模式
  • 享元(flyweight)模式
  • 外观(facade)模式
  • 适配器(adapter)模式
  • 装饰(decorator)模式
  • 观察者(observer)模式
  • 策略(strategy)模式
  • 持续更新中......

23种设计模式快速记忆的请看上面第一篇,本篇和大家一起来学习桥接模式相关内容。

设计模式适配器模式与桥接模式(设计模式系列桥接模式)(2)

模式定义

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

设计模式适配器模式与桥接模式(设计模式系列桥接模式)(3)

模式实现如下:

packagecom.niuh.designpattern.Bridge.v1; /** *桥接模式 */ publicclassBridgePattern{ publicstaticvoidmain(String[]args){ Implementorimple=newConcreteImplementorA(); abstractionabs=newRefinedAbstraction(imple); abs.Operation(); } } //实现化角色 interfaceImplementor{ voidOperationImpl(); } //具体实现化角色 classConcreteImplementorAimplementsImplementor{ publicvoidOperationImpl(){ System.out.println("具体实现化(ConcreteImplementor)角色被访问"); } } //抽象化角色 abstractclassAbstraction{ protectedImplementorimple; protectedAbstraction(Implementorimple){ this.imple=imple; } publicabstractvoidOperation(); } //扩展抽象化角色 classRefinedAbstractionextendsAbstraction{ protectedRefinedAbstraction(Implementorimple){ super(imple); } publicvoidOperation(){ System.out.println("扩展抽象化(RefinedAbstraction)角色被访问"); imple.OperationImpl(); } }

输出结果如下

扩展抽象化(RefinedAbstraction)角色被访问 具体实现化(ConcreteImplementor)角色被访问

解决的问题

在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。

模式组成

可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。

设计模式适配器模式与桥接模式(设计模式系列桥接模式)(4)

实例说明实例概况

某公司开发了一个财务管理系统,其中有个报表生成器的工具模块,客户可以指定任意一种报表类型,如基本报表,往来报表,资金报表,资产报表等,并且可以指定不同 的报表样式,如饼图,柱状图等。系统设计人员针对这个报表生成器的结构设计了如下图所示的类图。

设计模式适配器模式与桥接模式(设计模式系列桥接模式)(5)

后来在客户使用过程中,客户又希望增加一个新的报表和新的线形图,开发人员这个时候发现维护起来非常麻烦,设计人员经过仔细分析,发现存在严重的问题,因为新增加一个报表或者图,需要增加很多子类。所以,系统分析师最终对这个模块根据面向对象的设计原则对上面的方案进行了重构,重构后的图如下所示。

设计模式适配器模式与桥接模式(设计模式系列桥接模式)(6)

在本重构方案中,将报表和图形设计成两个继承结构,两者都可以独立变化,编程的时候可以只针对抽象类编码,而在运行的时候再将具体的图形子类对象注入到具体的 报表类中。这样的话,系统就具有良好的可扩展性和可维护性,并且满足了面向对象设计原则的开闭原则。

使用步骤

步骤1:定义实现化角色,报表接口

interfaceIReport{ voidoperationImpl(); }

步骤2:定义具体实现化角色(基本报表、往来报表、资金报表)

classBasicReportimplementsIReport{ @Override publicvoidoperationImpl(){ System.out.println("基本报表被访问."); } } classIntercourseReportimplementsIReport{ @Override publicvoidoperationImpl(){ System.out.println("往来报表被访问."); } } classCapitalReportimplementsIReport{ @Override publicvoidoperationImpl(){ System.out.println("资金报表被访问."); } }

步骤3:定义抽象化角色,图形

abstractclassAbstractionGraph{ protectedIReportiReport; publicAbstractionGraph(IReportiReport){ this.iReport=iReport; } abstractvoidoperation(); }

步骤4:定义扩展抽象化角色(柱状图、饼图)

classBarchartextendsAbstractionGraph{ publicBarchart(IReportiReport){ super(iReport); } @Override voidoperation(){ System.out.println("柱状图被访问."); iReport.operationImpl(); } } classPiechartextendsAbstractionGraph{ publicPiechart(IReportiReport){ super(iReport); } @Override voidoperation(){ System.out.println("饼图被访问."); iReport.operationImpl(); } }

步骤5:测试

publicclassBridgePattern{ publicstaticvoidmain(String[]args){ //实现化和抽象化分离 //基本报表 IReportbasicReport=newBasicReport(); //往来报表 IReportintercourseReport=newIntercourseReport(); //资金报表 IReportcapitalReport=newCapitalReport(); //基本报表使用柱状图 AbstractionGraphbarchart=newBarchart(basicReport); barchart.operation(); //基本报表使用饼图 AbstractionGraphpiechart=newPiechart(basicReport); piechart.operation(); } }

输出结果

柱状图被访问. 基本报表被访问. 饼图被访问. 基本报表被访问.

优点

桥接模式遵循了里氏替换原则和依赖倒置原则,最终实现了开闭原则,对修改关闭,对扩展开放。这里将桥接模式的优缺点总结如下。

桥接(Bridge)模式的优点:

  • 抽象与实现分离,扩展能力强
  • 符合开闭原则
  • 符合合成复用原则
  • 其实现细节对客户透明
缺点

由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。

应用场景

当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。

桥接模式通常适用于以下场景:

  1. 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时;
  2. 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时;
  3. 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

桥接模式的一个常见使用场景就是替换继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。

因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。

桥接模式模式的扩展

在软件开发中,有时桥接(Bridge)模式可与适配器模式联合使用。当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来,其结构图如下:

设计模式适配器模式与桥接模式(设计模式系列桥接模式)(7)

源码中的应用

JDBC驱动程序 ......

设计模式适配器模式与桥接模式(设计模式系列桥接模式)(8)

DriverManager类

DriverManager作为一个抽象化角色,聚合了实现化角色Connection,只不过与标准的桥梁模式不一样的是,DriverManager类下面没有子类。

//WorkermethodcalledbythepublicgetConnection()methods. privatestaticConnectiongetConnection( Stringurl,java.util.Propertiesinfo,Class<?>caller)throwsSQLException{ /* *WhencallerClisnull,weshouldchecktheapplication's *(whichisinvokingthisclassindirectly) *classloader,sothattheJDBCdriverclassoutsidert.jar *canbeloadedfromhere. */ ClassLoadercallerCL=caller!=null?caller.getClassLoader():null; synchronized(DriverManager.class){ //synchronizeloadingofthecorrectclassloader. if(callerCL==null){ callerCL=Thread.currentThread().getContextClassLoader(); } } if(url==null){ thrownewSQLException("Theurlcannotbenull","08001"); } println("DriverManager.getConnection(\"" url "\")"); //WalkthroughtheloadedregisteredDriversattemptingtomakeaconnection. //Rememberthefirstexceptionthatgetsraisedsowecanreraiseit. SQLExceptionreason=null; for(DriverInfoaDriver:registeredDrivers){ //Ifthecallerdoesnothavepermissiontoloadthedriverthen //skipit. if(isDriverAllowed(aDriver.driver,callerCL)){ try{ println("trying" aDriver.driver.getClass().getName()); Connectioncon=aDriver.driver.connect(url,info); if(con!=null){ //Success! println("getConnectionreturning" aDriver.driver.getClass().getName()); return(con); } }catch(SQLExceptionex){ if(reason==null){ reason=ex; } } }else{ println("skipping:" aDriver.getClass().getName()); } } //ifwegotherenobodycouldconnect. if(reason!=null){ println("getConnectionfailed:" reason); throwreason; } println("getConnection:nosuitabledriverfoundfor" url); thrownewSQLException("Nosuitabledriverfoundfor" url,"08001"); }

PS:以上代码提交在 Github

https://github.com/Niuh-Study/niuh-designpatterns.git

文章持续更新,可以公众号搜一搜「 一角钱技术 」第一时间阅读, 本文 GitHub org_hejianhui/JavaStudy 已经收录,欢迎 Star。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页