关于装饰模式和动态代理模式
装饰模式和动态代理模式乍一看差不多,都是动态的增加行为,其实有各自的区别。
一、首先我们看一下装饰设计模式,其基本思想如下:
1、编写一个类,实现与被装饰类相同的接口。目的使他们有相同的行为
2、定义一个实例变量,引用被装饰对象。目的和原来的老对象进行交接
3、定义构造方法,把被装饰对象注入进来。
4、对于不需要改写的方法,调用被装饰对象的。
5、对于要改写的方法,改写即可。
废话不多说,举一个例子,模拟实现一个数据库连接池,在这里,我想重写close方法,以实现调用close方法之后不是关闭连接,而是归还连接。
首先,继承java.sql.Connection接口,写一个类MyConnection:
1 package pool; 2 3 import java.sql.Array; 4 import java.sql.Blob; 5 import java.sql.CallableStatement; 6 import java.sql.Clob; 7 import java.sql.Connection; 8 import java.sql.DatabaseMetaData; 9 import java.sql.NClob; 10 import java.sql.PreparedStatement; 11 import java.sql.SQLClientInfoException; 12 import java.sql.SQLException; 13 import java.sql.SQLWarning; 14 import java.sql.SQLXML; 15 import java.sql.Savepoint; 16 import java.sql.Statement; 17 import java.sql.Struct; 18 import java.util.List; 19 import java.util.Map; 20 import java.util.Properties; 21 import java.util.concurrent.Executor; 22 23 //1、编写一个类,实现与被装饰类(com.mysql.jdbc.Connection)相同的接口。目的使他们有相同的行为 24 //2、定义一个实例变量,引用被装饰对象。目的和原来的老对象进行交接 25 //3、定义构造方法,把被装饰对象注入进来。 26 //4、对于不需要改写的方法,调用被装饰对象的。 27 //5、对于要改写的方法,改写即可。 28 29 public class MyConnection implements Connection { 30 31 private Connection conn; 32 private List<Connection> pool; 33 34 public MyConnection(Connection conn, List<Connection> pool){ 35 this.conn = conn; 36 this.pool = pool; 37 } 38 39 //把conn还回池中 40 @Override 41 public void close() throws SQLException { 42 pool.add(conn); 43 } 44 45 //静态代理 46 @Override 47 public <T> T unwrap(Class<T> iface) throws SQLException { 48 return conn.unwrap(iface); 49 } 50 51 @Override 52 public boolean isWrapperFor(Class<?> iface) throws SQLException { 53 return conn.isWrapperFor(iface); 54 }
除了重写的close方法进行改写,其余的都调用被装饰对象的,即conn.
接下来,继承标准的数据源,写一个MyDataSource,这里只演示重写getConnection()方法。
package pool; import java.awt.Menu; import java.io.PrintWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Logger; import javax.sql.DataSource; import day02.JDBCUtil; //继承标准的数据源 public class MyDataSource implements DataSource { //存放链接对象的池 private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>()); //最开始初始化一些链接到池中 static{ for (int i = 0; i < 10; i++) { Connection conn = JDBCUtil.getConnection(); pool.add(conn); } } @Override public Connection getConnection() throws SQLException { if(pool.size() > 0){ Connection connection = pool.remove(0); //使用装饰模式 MyConnection myConnection = new MyConnection(connection, pool); return myConnection; }else{ throw new RuntimeException("服务器忙"); } }
可以通过以下代码进行验证:
1 @Test 2 public void test1() throws SQLException{ 3 MyDataSource ds = new MyDataSource(); 4 //保存 5 Connection conn = ds.getConnection(); 6 System.out.println(conn.getClass().getName()); //输出MyConnection 7 //----------- 8 conn.close();//需要重新写一下close方法,不让它关闭而是归还连接 9 10 //删除 11 Connection conn1 = ds.getConnection(); 12 //----------- 13 conn1.close(); 14 }
二、对于动态代理模式,是AOP技术实现的关键,实现它有两种方式:
基于接口的动态代理:
前提:被代理对象的类,实现了至少一个接口
基于子类的动态代理:
借助第三方-CGLIB。
这里我们采用基于接口的动态代理进行演示刚才的例子:
不用写MyConnection了,只需要写MyDataSource:
package pool; import java.awt.Menu; import java.io.PrintWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Logger; import javax.sql.DataSource; import day02.JDBCUtil; //继承标准的数据源 public class MyDataSource implements DataSource { //存放链接对象的池 private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>()); //最开始初始化一些链接到池中 static{ for (int i = 0; i < 10; i++) { Connection conn = JDBCUtil.getConnection(); pool.add(conn); } } @Override public Connection getConnection() throws SQLException { if(pool.size() > 0){ final Connection connection = pool.remove(0);//使用动态代理模式 Connection proxyConnection = (Connection) Proxy.newProxyInstance( connection.getClass().getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("close".equals(method.getName())){ //用户调用close方法,返回池中 pool.add(connection); }else{ //其他方法,调用原来对象的对应方法 method.invoke(connection, args); } return null; } }); return proxyConnection; }else{ throw new RuntimeException("服务器忙"); } }
然后进行验证:
@Test public void test1() throws SQLException{ MyDataSource ds = new MyDataSource(); //保存 Connection conn = ds.getConnection(); System.out.println(conn.getClass().getName());//com.sun.proxy.$Proxy4 //----------- conn.close();//需要重新写一下close方法,不让它关闭而是归还连接 //删除 Connection conn1 = ds.getConnection(); //----------- conn1.close(); }
对装饰器模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个 接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。此外,不论我们使用哪一个模式,都可以很容易地在真实对象的方法前面或者后面加上自定义的方法。
然而,实际上,在装饰器模式和代理模式之间还是有很多差别的。装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模 式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。
我们可以用另外一句话来总结这些差别:使用代理模式,代理和真实对象之间的的关系通常在编译时就已经确定了,而装饰者能够在运行时递归地被构造。