以手机操作问题引入桥接模式
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:
传统思路分析
传统方案解决手机操作问题分析
1)扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
2)违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本。
问题的根本原因在于我们试图在两个独立的维度上进行扩展。这在处理继承时是很常见的问题。
解决思路:通过抽取其中一个维度并使之成为独立的类层次, 这样就可以在初始类中引用这个新层次的对象, 从而使得一个类不必拥有所有的状态和行为。
将实现与抽象放在两个不同的层次中,使两个层次可以独立改变。
桥接模式基于类的最小设计原则,通过使用封装、聚合、继承等行为让不同的类承担不同的职责。
它的主要特点是把抽象与行为实现分离开,从而可以保持各部分的独立性以及功能扩展。
品牌及其实现类
- public interface Brade {
- //开机
- void open();
- //打电话;
- void call();
- //关机;
- void close();
- }
- public class HuaWei implements Brade{
-
- public void open() {
- System.out.println("华为手机开机");
- }
-
- public void call() {
- System.out.println("华为手机通话");
- }
-
- public void close() {
- System.out.println("华为手机关机");
- }
- }
- public class Vivo implements Brade{
-
- public void open() {
- System.out.println("Vivo手机开机");
- }
-
- public void call() {
- System.out.println("Vivo手机通话");
- }
-
- public void close() {
- System.out.println("Vivo手机关机");
- }
- }
- public class XiaoMi implements Brade{
-
- public void open() {
- System.out.println("小米手机开机");
- }
-
- public void call() {
- System.out.println("小米手机通话");
- }
-
- public void close() {
- System.out.println("小米手机关机");
- }
- }
抽象类手机及其实现类
- //抽象类 手机
- public abstract class Phone {
- //聚合手机品牌;
- private Brade brade;
-
- public Phone(Brade brade) {
- this.brade = brade;
- }
-
- //调用品牌的方法;
- protected void open(){
- this.brade.open();
- }
-
- protected void call(){
- this.brade.call();
- }
-
- protected void close(){
- this.brade.close();
- }
- }
- public class Folded extends Phone {
- public Folded(Brade brade) {
- super(brade);
- }
-
- @Override
- public void open(){
- System.out.println("折叠手机");
- super.open();
- }
-
- @Override
- public void call(){
- System.out.println("折叠手机");
- super.call();
- }
-
- @Override
- public void close(){
- System.out.println("折叠手机");
- super.close();
- }
- }
- public class UpRight extends Phone {
- public UpRight(Brade brade) {
- super(brade);
- }
-
- @Override
- public void open(){
- System.out.println("直立手机");
- super.open();
- }
-
- @Override
- public void call(){
- System.out.println("直立手机");
- super.call();
- }
-
- @Override
- public void close(){
- System.out.println("直立手机");
- super.close();
- }
- }
客户端
- public class Client {
- public static void main(String[] args) {
-
- //选择折叠式华为手机操作
- Phone phone1=new Folded(new HuaWei());
- phone1.open();
- phone1.call();
-
- System.out.println("<------------------------------->");
-
- //使用直立式小米手机操作;
- Phone phone2 = new UpRight(new XiaoMi());
- phone2.call();
- phone2.close();
-
- }
- }
结果:
我们来张图了解下JDBC的使用框架大概是个什么样子
JDBC为所有的关系型数据库提供一个通用的标准,这就是一个桥接模式的典型应用。我们先回顾一下JDBC的使用,用JDBC连接MySQL数据库主要分为这样几步:
- //1.加载MySQL驱动注入到DriverManager
- Class.forName("com.mysql.cj.jdbc.Driver");
- //2.提供JDBC连接的URL、用户名和密码
- String url = "jdbc:mysql://localhost:3306/test_db?";
- String username = "root";
- String password = "root";
- //3.创建数据库的连接
- Connection connection = DriverManager.getConnection(url, username, password);
- //4.创建statement实例
- Statement statement = connection.createStatement();
- //5.执行SQL语句
- String query = "select * from test"; //查询语句,也可以换成CRUD的其他语句
- ResultSet resultSet = statement.executeQuery(query);
- //6.关闭连接对象
- connection.close();
我们一步步来看,先看步骤1:
Class.forName("com.mysql.cj.jdbc.Driver");
查看对应的源码
- public class Driver extends NonRegisteringDriver implements java.sql.Driver {
- public Driver() throws SQLException {
- }
-
- static {
- try {
- java.sql.DriverManager.registerDriver(new Driver());
- } catch (SQLException E) {
- throw new RuntimeException("Can't register driver!");
- }
- }
- }
是通过静态方法调用registerDriver()
方法来将MySQL驱动注入到DriverManager
,registerDriver()
方法具体如下:向registeredDrivers添加驱动
- public static synchronized void registerDriver(java.sql.Driver driver)
- throws SQLException {
- //直接调用下面的同名静态方法
- registerDriver(driver, null);
- }
-
- public static synchronized void registerDriver(java.sql.Driver driver,DriverAction da)throws SQLException {
- /* registeredDrivers是一个list,用DriverInfo实例封装Driver */
- if(driver != null) {
- registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
- } else {
- // This is for compatibility with the original DriverManager
- throw new NullPointerException();
- }
- println("registerDriver: " + driver);
-
- }
registeredDrivers
静态变量其实是一个list:
- public class DriverManager {
- // List of registered JDBC drivers
- private final static CopyOnWriteArrayList
registeredDrivers = new CopyOnWriteArrayList<>(); - //...
- }
而DriverInfo
类中封装了java.sql.Driver
接口:
- class DriverInfo {
-
- final Driver driver;
- DriverAction da;
- DriverInfo(Driver driver, DriverAction action) {
- this.driver = driver;
- da = action;
- }
- //...
- }
java.sql.Driver
接口:
- public interface Driver {
-
- Connection connect(String url, java.util.Properties info)
- throws SQLException;
-
- boolean acceptsURL(String url) throws SQLException;
-
- DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
- throws SQLException;
-
- int getMajorVersion();
-
- int getMinorVersion();
-
- boolean jdbcCompliant();
-
- public Logger getParentLogger() throws SQLFeatureNotSupportedException;
- }
再看步骤2、3,重点是步骤3
Connection connection = DriverManager.getConnection(url, username, password);
Connection
接口是和特定数据库的连接会话,不同的数据库的连接会话都不相同:
- public interface Connection extends Wrapper, AutoCloseable {
-
- Statement createStatement() throws SQLException;
- //...
- }
是通过DriverManager
中的getConnection
方法,从registeredDrivers
进行选择对应数据库驱动下的连接实例,再通过数据库驱动获取连接
- public static Connection getConnection(String url,String user, String password) throws SQLException {
- java.util.Properties info = new java.util.Properties();
-
- if (user != null) {
- info.put("user", user);
- }
- if (password != null) {
- info.put("password", password);
- }
-
- return (getConnection(url, info, Reflection.getCallerClass()));
- }
- // 实际上调用的是下面的静态方法getConnection
- // Worker method called by the public getConnection() methods.
- private static Connection getConnection(
- String url, java.util.Properties info, Class> caller) throws SQLException {
- /*
- * When callerCl is null, we should check the application's
- * (which is invoking this class indirectly)
- * classloader, so that the JDBC driver class outside rt.jar
- * can be loaded from here.
- */
- ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
- synchronized(DriverManager.class) {
- // synchronize loading of the correct classloader.
- if (callerCL == null) {
- callerCL = Thread.currentThread().getContextClassLoader();
- }
- }
-
- if(url == null) {
- throw new SQLException("The url cannot be null", "08001");
- }
-
- println("DriverManager.getConnection(\"" + url + "\")");
-
- // Walk through the loaded registeredDrivers attempting to make a connection.
- // Remember the first exception that gets raised so we can reraise it.
- SQLException reason = null;
-
- for(DriverInfo aDriver : registeredDrivers) {
- // If the caller does not have permission to load the driver then
- // skip it.
- if(isDriverAllowed(aDriver.driver, callerCL)) {
- try {
- println(" trying " + aDriver.driver.getClass().getName());
- Connection con = aDriver.driver.connect(url, info);
- if (con != null) {
- // Success!
- println("getConnection returning " + aDriver.driver.getClass().getName());
- return (con);
- }
- } catch (SQLException ex) {
- if (reason == null) {
- reason = ex;
- }
- }
-
- } else {
- println(" skipping: " + aDriver.getClass().getName());
- }
- }
-
- // if we got here nobody could connect.
- if (reason != null) {
- println("getConnection failed: " + reason);
- throw reason;
- }
-
- println("getConnection: no suitable driver found for "+ url);
- throw new SQLException("No suitable driver found for "+ url, "08001");
- }
综上我们可以画出对应的类图:
Driver和Connection之间是通过DriverManager类进行桥连接的
那在JDBC的设计中,什么是“抽象”,什么是“实现”呢?JDBC抽象出来的那套API就是抽象,不同数据库服务商实现的Driver就是实现。所以桥接模式中的抽象并不是指一个接口,它可能是设计出来的一套跟数据库操作相关的API。而具体的实现也不是一个接口,也可能是一套API,就像Driver中的connect,execute等方法。其实调用到最后,DriverManager都是委托具体的Driver干活的(connect,execute)
参考文章:你知道桥接模式在JDBC中的应用吗_归斯君的博客-CSDN博客_jdbc桥接模式
【设计模式系列】餐补:桥接模式与JDBC的故事 - 掘金 (juejin.cn)
(1)实现了抽象和实现部分的分离
桥接模式分离了抽象部分和实现部分,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,分别定义接口,这有助于系统进行分层设计,从而产生更好的结构化系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。
(2)更好的可扩展性
由于桥接模式把抽象部分和实现部分分离了,从而分别定义接口,这就使得抽象部分和实现部分可以分别独立扩展,而不会相互影响,大大的提供了系统的可扩展性。
(3)可动态的切换实现
由于桥接模式实现了抽象和实现的分离,所以在实现桥接模式时,就可以实现动态的选择和使用具体的实现。
(4)实现细节对客户端透明,可以对用户隐藏实现细节。
(1)桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。
1)JDBC 驱动程序
2)银行转账系统
3)消息管理