浅谈java中的装饰模式和动态代理模式

什么是装饰模式

    定义:装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

    单单看上面的定义太抽象了,我们来举个栗子。

我们先定义一个Animal接口,里面有一个eat和call方法

public interface Animal {
	public void eat();
	public void call();
    }

再创建一个Dog类,实现Animal接口

public class Dog implements Animal{

	public void eat(){
		System.out.println("狗在嘚嘚的吃");
	}
	
	public void call(){
		System.out.println("狗在旺旺的叫");
	}
}

现在我们不喜欢狗的叫声,想让狗哈哈哈地叫,该怎么办呢。可以继承Dog类,然后重写call方法;也可以使用装饰模式。下面我们使用装饰模式来实现一下。

我们创建一个ZhuangshiDog类

/**
 * 装饰类 要实现和被装饰类一样的接口
 * @author wangchaoyouziying
 *
 */
public class ZhuangshiDog implements Animal {

	private Animal ani;
	
	public ZhuangshiDog(Animal ani) {
		this.ani = ani;
	}
	
	@Override
	public void eat() {
		ani.eat();
	}

	@Override
	public void call() {
		System.out.println("狗在哈哈哈地叫");
	}

}

测试一下

public class Test {

	public static void main(String[] args) {
		Animal dog = new Dog();//这里Dog是被装饰者
		
		//实例化装饰类
		Animal zsDog = new ZhuangshiDog(dog);
		zsDog.call();
		zsDog.eat();
	}

}
输出结果:
狗在哈哈哈地叫
狗在嘚嘚的吃

装饰模式的优点:可以不改变被装饰者和继承关系的情况下,拓展被装饰者的功能。

缺点:需要实现和被装饰者相同的接口,那么如果该接口有一百个方法,而我们只想装饰其中的一个方法。此时我们不得不另外实现其余的99个方法,很明显增加了代码冗余量。

扫描二维码关注公众号,回复: 1545911 查看本文章

总结:装饰模式适用于被装饰者实现的接口中方法数较少的情况。

那么对于方法数较多的情况下该怎么办呢。此时可以使用动态代理模式。


什么是动态代理模式?

    定义:用来修改已经具有的对象的方法,控制方法是否执行,或在方法执行之前和执行之后做一些额外的操作。

我们同样来举个栗子。

我们先定义一个Animal接口,里面有一个eat和call方法

package cn.chao.zhuangshiProxy;

public interface Animal {
	public void eat();
	public void call();
}

创建一个Dog类,实现Animal接口

public class Dog implements Animal{

	public void eat(){
		System.out.println("狗在嘚嘚的吃");
	}
	
	public void call(){
		System.out.println("狗在旺旺的叫");
	}
}

再创建一个代理类ProxyDog

/**
 * 动态代理类 给所有的Animal创建代理对象 代理对象,不需要实现接口
 * 
 * @author wangchaoyouziying
 *
 */
public class ProxyDog {

	private Object target;

	public ProxyDog(Object target) {
		this.target = target;
	}

	/**
	 * 给被代理对象生成代理对象
	 * 
	 * @return
	 */
	public Object getProxyInstance() {
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 被代理对象的类加载器
				target.getClass().getInterfaces(), // 被代理对象所实现的所有接口组成的数组
				new InvocationHandler() {

					/**
					 * proxy	代理对象
					 * method	被代理对象的方法
					 * args		被代理对象的方法中的参数
					 * 返回值	被代理对象的方法的返回值
					 */
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						if ("call".equals(method.getName())) {// 对被代理的Dog类的call方法进行改造
							System.out.println("狗在嘻嘻嘻地笑");
							return null;	//被代理对象的call方法没有返回值,所以这里返回null
						} else {//其他方法不改造,则调用method的invoke方法
							return method.invoke(target, args);//target-->被代理对象		args-->被代理对象的方法的参数
						}
					}
				});
	}
}

测试一下:

public class Test {

	public static void main(String[] args) {
		Animal dog = new Dog();//这里的dog是被代理对象
		
		//实例化装饰类
//		Animal zsDog = new ZhuangshiDog(dog);
//		zsDog.call();
//		zsDog.eat();
		Animal proxyDog = (Animal) new ProxyDog(dog).getProxyInstance();
		proxyDog.call();
		proxyDog.eat();
	}

}

输出结果:

狗在嘻嘻嘻地笑

狗在嘚嘚的吃


说了那么多,动态代理究竟有什么用呢?

比如我们有一个需求,手写一个数据库连接池,我们执行完sql语句查询出结果后要将当前数据库的连接还回到连接池吧,我们只需调用自定义的连接池MyPool类的returnConn方法就可以了。但是万一程序员忘了调用returnConn方法了,而是调用了Connection的close方法,那当前连接就回不到连接池了。这时我们就需要通过动态代理,改造一下close方法。

首先,新建一个自定义数据库连接池类MyPool,实现javax.sql.DataSource接口

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.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

import javax.sql.DataSource;

public class MyPool implements DataSource {

	// LinkList底层是链表,方便增删,不方便查找
	private static List<Connection> pool = new LinkedList<Connection>();

	static {
		try {
			// 注册mysql驱动
			Class.forName("com.mysql.jdbc.Driver");
			for (int i = 0; i < 5; i++) {// 向数据库连接池中创建5个连接
				// 创建连接
				Connection conn = DriverManager.getConnection("jdbc:mysql:///ccm", "root", "123456");
				pool.add(conn);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException();
		}
	}

	@Override
	public Connection getConnection() throws SQLException {
		if (pool.size() == 0) {// 用户获取连接的时候,如果连接池中没有连接可用,则创建3条连接
			for (int i = 0; i < 3; i++) {
				Connection conn = DriverManager.getConnection("jdbc:mysql:///ccm", "root", "123456");
				pool.add(conn);
			}
		}
		Connection conn = pool.remove(0);// 从数据库连接池中取一个连接

		// 利用动态代理改造Connection类的close方法,这样我们只要调用Connection的close方法就会将连接还回到连接池中
		Connection proxyConn = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(),
				conn.getClass().getInterfaces(), new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						if ("close".equals(method.getName())) {
							// 对于想要改造的close方法,我们自己写
							returnConn(conn);
							return null;
						} else {
							// 对于不想改造的方法,调用被代理对象原来的方法
							return method.invoke(conn, args);
						}
					}
				});
		System.out.println("获取了一个连接,现在池里还有" + pool.size() + "个连接");
		return proxyConn;
	}

	/**
	 * 将数据库连接还回到连接池中
	 */
	protected void returnConn(Connection conn) {
		try {
			if (conn != null && !conn.isClosed()) {
				pool.add(conn);
				System.out.println("还回了一个连接,现在池里还有" + pool.size() + "个连接");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public PrintWriter getLogWriter() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setLogWriter(PrintWriter out) throws SQLException {
		// TODO Auto-generated method stub

	}

	@Override
	public void setLoginTimeout(int seconds) throws SQLException {
		// TODO Auto-generated method stub

	}

	@Override
	public int getLoginTimeout() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

}

此时我们在jdbc中调用Connection改造过后的close方法就会帮我们将当前连接还回到连接池了。

jdbc的实现类:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class JdbcTest {

	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		MyPool pool = new MyPool();
		
		try{
			conn = pool.getConnection();
			ps = conn.prepareStatement("select * from users");
			rs = ps.executeQuery();
			while(rs.next()){
				String username = rs.getString("username");
				System.out.println(username);
			}
		}catch (Exception e) {
			e.printStackTrace();
		}finally{
			if(conn != null){
				try{
					conn.close();//调用close方法时,会将连接还回到连接池中,而不会将连接关闭
				}catch (Exception e) {
					e.printStackTrace();
				}finally{
					conn = null;
				}
			}
			if(ps != null){
				try{
					ps.close();
				}catch (Exception e) {
					e.printStackTrace();
				}finally{
					ps = null;
				}
			}
			if(rs != null){
				try{
					rs.close();
				}catch(Exception e){
					e.printStackTrace();
				}finally{
					rs = null;
				}
			}
		}
	}
}

运行后的结果:

获取了一个连接,现在池里还有4个连接
川哥
晴姐
小红
还回了一个连接,现在池里还有5个连接

动态代理总结:动态代理不需要实现接口,但是被代理对象一定要实现至少一个接口,否则不能用动态代理。

源码:点击打开链接

猜你喜欢

转载自blog.csdn.net/wang1171405487/article/details/80632060