一、适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。
适配器的特点在于兼容,从代码上的特点来说,适配类与原有的类具有相同的接口,并且持有新的目标对象。
是将一个类(a)通过某种方式转换成另一个类(b)。
http://www.runoob.com/design-pattern/adapter-pattern.html
https://blog.csdn.net/lulei9876/article/details/39994825#
二、代理模式(Proxy):一个类代表另一个类的功能。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。(静态代理)
抽象角色:声明真实对象和代理对象的共同接口,这样可在任何使用真实对象的地方都可以使用代理对象。
真实角色:即为代理对象所代表的目标对象,代理角色所代表的真实对象,是我们最终要引用的对象。
代理角色:代理对象内部含有真实对象的引用,从而可以在任何时候操作真实对象。代理对象提供一个与真实对象相同的接口,以便可以在任何时候替代真实对象。代理对象通常在客户端调用传递给真实对象之前或之后,执行某个操作,而不是单纯地将调用传递给真实对象,同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
Subject抽象角色、RealSubject真实角色、Proxy代理角色。其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口;RealSubject角色用来真正完成业务服务功能;Proxy角色负责将自身的request请求,调用RealSubject对应的request功能来实现业务功能,自己不真正做业务。
- subject类:抽象角色,定义了RealSubject和Proxy的共用接口,这样就能在任何使用RealSubject的地方都可以使用Proxy;
- RealSubject类:真实角色,定义Proxy所代表的真实实体;
- Proxy类:代理角色,用来代替实体,保存一个引用,使代理可以访问实体,并提供一个与Subject的接口相同的接口。
http://www.runoob.com/design-pattern/proxy-pattern.html
动态代理:Jdk代理和cglib代理 AOP
https://www.cnblogs.com/gaoqing/p/3801679.html
jdk动态代理:jdk的动态代理是基于接口的,必须实现了某一个或多个任意接口才可以被代理,并且只有这些接口中的方法会被代理。
在调用过程中使用了通用的代理类包装了RealSubject实例,然后调用了Jdk的代理工厂方法实例化了一个具体的代理类。最后调用代理的doSomething方法,还有附加的before、after方法可以被任意复用(只要我们在调用代码处使用这个通用代理类去包装任意想要需要包装的被代理类即可)。当接口改变的时候,虽然被代理类需要改变,但是我们的代理类却不用改变了。这个调用虽然足够灵活,可以动态生成一个具体的代理类,而不用自己显示的创建一个实现具体接口的代理类。
三、装饰器模式:丰富原接口的功能,并且不改动原先的接口
http://www.runoob.com/design-pattern/decorator-pattern.html
https://blog.csdn.net/lulei9876/article/details/39994825#
四、桥接模式:将抽象部分与实现部分分离,使它们都可以独立的变化。
https://www.cnblogs.com/-crazysnail/p/3977815.html
1、意图:
将抽象部分(抽象接口)与它的实现部分(代码实现)分离,使它们都可以独立地变化。
理解:抽象部分是对外展现的接口(api),而实现部分是针对抽象接口提供的不同版本的功能实现,使两者独立变化指两者可以在各自的维度上自由变化,而不会产生太大的影响。如可以在api中添加新的接口,而不影响具体实现部分;可以在实现部分针对特定接口添加新的实现方式,而不影响抽象接口定义。
桥接模式将类的继承关系转变为类的聚合关系(见下图)。对于抽象接口及其实现,Java中通常的实现方式是通过抽象类的继承或接口的实现,但是桥接模式,将这种抽象接口与实现之间的关系变成了聚合关系。
2、桥接模式(也称:桥梁模式)类图
角色:
- Abstraction:定义抽象接口,对外提供api;自身维护一个Implementor(桥)的引用,使用Implementor的操作来完成自身接口功能,如operation方法通过impl.doOperation()来实现;
- RefinedAbstraction:扩展Abstraction的抽象接口;
- Implementor:架在抽象接口与不同实现方式之间的桥,且自身的接口不必与Abstraction完全相同;
- ConcreteImplementorA:实现Implementor(桥)的接口,针对Implementor(桥)中的接口doOperation提供特色化的实现;
- Client:用户类。
协作:
- Client调用Abstraction提供的接口,而Abstraction将Client的调用通过Implementor来实现。
3、JDBC与桥接:
JDBC是桥接模式的典型实现。
先看下类图:
通常使用JDBC连接数据库时,会使用如下代码:
1 Class.forName("数据库类驱动器");
2 Connection conn = DriverManager.getConnection("数据库url", "用户名", "密码");
3 //.................
针对不同的数据库,JDBC都可以通过java.sql.DriverManager类的静态方法getConnection(数据库url, 用户名, 密码)来获取数据库的连接。JDBC通过DriverManager对外提供了操作数据库的统一接口getConnection,通过该方法可以获取不同数据库的连接,并且通过Connection类提供的接口来进行数据的查询操作。
JDBC为不同的数据库操作提供了相同的接口,但是JDBC本身并没有针对每种数据库提供一套具体实现代码,而是通过接口java.sql.Driver的connect方法连接到了不同的数据库实现。
1 public interface Driver
2 {
3 public abstract Connection connect(String s, Properties properties) throws SQLException;
4 //其他方法
5 }
在JDBC中,针对不同数据库提供的统一的操作接口通过java.sql.Driver(桥)连接到了不同的数据库实现。如连接mysql数据库。
1 package com.mysql.jdbc;
2 public class NonRegisteringDriver implements java.sql.Driver //对java.sql.Driver接口提供了实现
3 {
4 public Connection connect(String url, Properties info) throws SQLException
5 {
6 //实现
7 }
8 //其他方法
9 }
Java在连接MySQL时需要使用mysql-connector-java.jar,mysql-connector-java.jar包提供了对MySQL数据库操作的具体实现,并通过接口Driver连接到了JDBC统一的api。
4、JDBC中桥接模式具体如何实现?
既然,针对不同的数据库,通过DriverManger.getConnection()可以获得相同的Connection接口,那先看DriverManager的源码:
1 public class DriverManager
2 {
3 private static final CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList();
4 //存放DriverInfo的链表
5 public static synchronized void registerDriver(Driver driver) throws SQLException
7 {
8 if(driver != null)
9 registeredDrivers.addIfAbsent(new DriverInfo(driver));
10 //向链表中添加DriverInfo实例,DriverInfo封装了Driver
11 else
12 throw new NullPointerException();
13 println((new StringBuilder()).append("registerDriver: ").append(driver).toString());
14 }
15 private static Connection getConnection(String s, Properties properties, Class class1)
16 throws SQLException
17 {
18 //.....
19 Iterator iterator = registeredDrivers.iterator(); //遍历registeredDrivers表
20 do
21 {
22 if(!iterator.hasNext())
23 break;
24 DriverInfo driverinfo = (DriverInfo)iterator.next();
25 if(isDriverAllowed(driverinfo.driver, classloader))
26 try
27 {
28 Connection connection = driverinfo.driver.connect(s, properties);
//调用Driver接口提供的connect方法来获取Connection对象
29 if(connection != null)
30 {
31 return connection;
32 }
33 }
34 catch(SQLException sqlexception1)
35 {
36 if(sqlexception == null)
37 sqlexception = sqlexception1;
38 }
39 } while(true);
40 }
41 //其他方法
42 }
从DriverManager.getConnection()源码可见,方法中遍历了包含DriverInfo实例的表registeredDrivers,通过表中实例driverinfo来获取封装的java.sql.Driver类型的实例,并调用java.sql.Driver接口的connect方法获取到Connection。
【注:DriverInfo是Driver的封装类。由DriverInfo源码可见。
1 class DriverInfo
2 {
3
4 DriverInfo(Driver driver1)
5 {
6 driver = driver1;
7 }
8
9 public boolean equals(Object obj)
10 {
11 return (obj instanceof DriverInfo) && driver == ((DriverInfo)obj).driver;
12 }
13
14 public int hashCode()
15 {
16 return driver.hashCode();
17 }
18
19 public String toString()
20 {
21 return (new StringBuilder()).append("driver[className=").append(driver).append("]").toString();
22 }
23
24 final Driver driver;
25 }
那么,Driver实例是何时注入到DriverManager类的registeredDrivers中的呢?以mysql为例,在每次使用JDBC连接mysql时,都会有下面的调用:
1 Class.forName("com.mysql.jdbc.Driver");
该行代码通过反射加载了com.mysql.jdbc.Driver类(com.mysql.jdbc.Driver类在mysql-connector-java.jar中,而mysql-connector-java.jar是JDBC连接MySQL的jar包),查看com.mysql.jdbc.Driver类:
1 package com.mysql.jdbc;
2
3 public class Driver extends NonRegisteringDriver
4 implements java.sql.Driver
5 {
6
7 public Driver()
8 throws SQLException
9 {
10 }
11
12 static
13 {
14 try
15 {
16 DriverManager.registerDriver(new Driver());
17 }
18 catch(SQLException E)
19 {
20 throw new RuntimeException("Can't register driver!");
21 }
22 }
23 }
在com.mysql.jdbc.Driver的源码中可以看到在加载com.mysql.jdbc.Driver类时,通过类中的静态域中的红色代码,会调用DriverManager的registerDriver方法将当前MySQL的驱动类实例注入到DriverManager的registeredDrivers中。
通过整个代码调用,展示了桥接模式在JDBC中是如何运用的。
5、适用性:
- 当不希望抽象接口和实现部分采用固定的绑定关系时;
6、特点:
- 桥接模式良好地实现了开闭原则:通常应用桥接模式的地方,抽象接口和具体实现部分都是可以变化的,且抽象接口与实现耦合度低;
- 桥接模式将类继承关系转换成了对象组合关系,实现了类的复用,减少了类的个数。