什么是装饰模式
定义:装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
单单看上面的定义太抽象了,我们来举个栗子。
我们先定义一个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个方法,很明显增加了代码冗余量。
总结:装饰模式适用于被装饰者实现的接口中方法数较少的情况。
那么对于方法数较多的情况下该怎么办呢。此时可以使用动态代理模式。
什么是动态代理模式?
定义:用来修改已经具有的对象的方法,控制方法是否执行,或在方法执行之前和执行之后做一些额外的操作。
我们同样来举个栗子。
我们先定义一个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 pool = new LinkedList();
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 unwrap(Class 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个连接
动态代理总结:动态代理不需要实现接口,但是被代理对象一定要实现至少一个接口,否则不能用动态代理。
源码:点击打开链接