1.介绍
桥接模式(Bridge 模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
是一种结构型设计模式
桥接模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
2.问题
假如你有一个几何 形状
Shape类, 从它能扩展出两个子类: 圆形
Circle和 方形
Square 。 你希望对这样的类层次结构进行扩展以使其包含颜色, 所以你打算创建名为 红色
Red和 蓝色
Blue的形状子类。 但是, 由于你已有两个子类, 所以总共需要创建四个类才能覆盖所有组合, 例如 蓝色圆形
BlueCircle和 红色方形
RedSquare 。
在层次结构中新增形状和颜色将导致代码复杂程度指数增长。 例如添加三角形状, 你需要新增两个子类, 也就是每种颜色一个; 此后新增一种新颜色需要新增三个子类, 即每种形状一个。 如此以往, 情况会越来越糟糕。
3.解决方案
问题的根本原因是我们试图在两个独立的维度——形状与颜色——上扩展形状类。 这在处理类继承时是很常见的问题。
桥接模式通过将继承改为组合的方式来解决这个问题。 具体来说, 就是抽取其中一个维度并使之成为独立的类层次, 这样就可以在初始类中引用这个新层次的对象, 从而使得一个类不必拥有所有的状态和行为。
红色
和 蓝色
两个子类的颜色类中, 然后在 形状
类中添加一个指向某一颜色对象的引用成员变量。 现在, 形状类可以将所有与颜色相关的工作委派给连入的颜色对象。 这样的引用就成为了 形状
和 颜色
4.
5.代码示例
案例场景:进行项目开发的时候,都会有service这个实现业务逻辑的类,业务中往往会涉及到日志的输出(如log4j和logback),还有涉及到数据库的操作(如mongodb和mysql),这个时候我们就希望就日志输出和数据库操作抽象出来,在客户端运行的时候进行决定
代码结构:
日志操作抽象部分:
public interface Logger { public void info(String message); public void debug(String debug); }
日志操作log4j实现:
public class Log4jLogger implements Logger{ @Override public void info(String message) { System.out.println("log4j->[INFO]" + message); } @Override public void debug(String message) { System.out.println("log4j->[debug]" + message); } }
日志操作logback实现:
public class LogbackLogger implements Logger{ @Override public void info(String message) { System.out.println("logback->[INFO]" + message); } @Override public void debug(String message) { System.out.println("logback->[debug]" + message); } }
数据库操作抽象部分:
public interface DbOperator { public void insert(Object obj); }
数据库操作mysql实现:
public class MysqlOperator implements DbOperator { @Override public void insert(Object obj) { System.out.println(obj + "已写⼊Mysql"); } }
数据库操作mongodb实现:
public class MongoOperator implements DbOperator { @Override public void insert(Object obj) { System.out.println(obj + "已写⼊MongoDB"); } }
业务操作抽象部分:
public interface Service { public void init(); }
业务部分用户插入操作:
public class UserService implements Service { private DbOperator dbOperator = null; private Logger logger = null; public UserService(DbOperator dbOperator, Logger logger) { this.dbOperator = dbOperator; this.logger = logger; } public void create(){ dbOperator.insert("{⽤户A数据}"); logger.debug("数据插⼊成功"); } @Override public void init() { logger.info("UserService已初始化完毕"); } }
业务部分用户更新操作:
public class EmployeeService implements Service { private DbOperator dbOperator = null; private Logger logger = null; public EmployeeService(DbOperator dbOperator, Logger logger) { this.dbOperator = dbOperator; this.logger = logger; } public void update(){ dbOperator.insert("{员⼯A数据}"); logger.info("数据更新成功"); } @Override public void init() { logger.info("EmployeeService已初始化完毕"); } }
如上的代码中,还不知道业务在运行时使用的数据库和日志
客户端:业务初始化时,根据传入参数的不同,选择不同的数据库和日志操作
public class Client { public static void main(String[] args) { EmployeeService employeeService = new EmployeeService(new MongoOperator(),new Log4jLogger()); employeeService.init(); employeeService.update(); System.out.println("======================="); UserService userService = new UserService(new MysqlOperator(), new LogbackLogger()); userService.init(); userService.create(); } }
6.优点
1、分离抽象接⼝及其实现部分。提⾼了⽐继承更好的解决⽅案。
2、桥接模式提⾼了系统的可扩充性,在两个变化维度中任意扩展⼀个维度,都不需要修改原有系统。
3、实现细节对客户透明,可以对⽤户隐藏实现细节。
7.缺点
1、桥接模式的引⼊会增加系统的理解与设计难度,由于聚合关联关系建⽴在抽象层,要求开发者针对抽象进⾏设计与编程。
2、桥接模式要求正确识别出系统中两个独⽴变化的维度,因此其使⽤范围具有⼀定的局限性。
8.使用场景
如果⼀个系统需要在构件的抽象化⻆⾊和具体化⻆⾊之间增加更多的灵活性,避免在两个层次之间建⽴静态的继承联系,通过桥接模式可以使它们在抽象层建⽴⼀个关联关系。
对于那些不希望使⽤继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适⽤。
⼀个类存在两个独⽴变化的维度,且这两个维度都需要进⾏扩展。
9.模式总结
桥接模式实现了抽象化与实现化的脱耦。他们两个互相独⽴,不会影响到对⽅。
对于两个独⽴变化的维度,使⽤桥接模式再适合不过了。
对于”具体的抽象类”所做的改变,是不会影响到客户。