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

叁歲伎倆 2022-05-23 12:57 294阅读 0赞

什么是装饰模式

  1. 定义:装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
  2. 单单看上面的定义太抽象了,我们来举个栗子。

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

  1. public interface Animal {
  2. public void eat();
  3. public void call();
  4. }

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

  1. public class Dog implements Animal{
  2. public void eat(){
  3. System.out.println("狗在嘚嘚的吃");
  4. }
  5. public void call(){
  6. System.out.println("狗在旺旺的叫");
  7. }
  8. }

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

我们创建一个ZhuangshiDog类

  1. /**
  2. * 装饰类 要实现和被装饰类一样的接口
  3. * @author wangchaoyouziying
  4. *
  5. */
  6. public class ZhuangshiDog implements Animal {
  7. private Animal ani;
  8. public ZhuangshiDog(Animal ani) {
  9. this.ani = ani;
  10. }
  11. @Override
  12. public void eat() {
  13. ani.eat();
  14. }
  15. @Override
  16. public void call() {
  17. System.out.println("狗在哈哈哈地叫");
  18. }
  19. }

测试一下

  1. public class Test {
  2. public static void main(String[] args) {
  3. Animal dog = new Dog();//这里Dog是被装饰者
  4. //实例化装饰类
  5. Animal zsDog = new ZhuangshiDog(dog);
  6. zsDog.call();
  7. zsDog.eat();
  8. }
  9. }

输出结果:

  1. 狗在哈哈哈地叫
  2. 狗在嘚嘚的吃

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

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

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

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

什么是动态代理模式?

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

我们同样来举个栗子。

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

  1. package cn.chao.zhuangshiProxy;
  2. public interface Animal {
  3. public void eat();
  4. public void call();
  5. }

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

  1. public class Dog implements Animal{
  2. public void eat(){
  3. System.out.println("狗在嘚嘚的吃");
  4. }
  5. public void call(){
  6. System.out.println("狗在旺旺的叫");
  7. }
  8. }

再创建一个代理类ProxyDog

  1. /**
  2. * 动态代理类 给所有的Animal创建代理对象 代理对象,不需要实现接口
  3. *
  4. * @author wangchaoyouziying
  5. *
  6. */
  7. public class ProxyDog {
  8. private Object target;
  9. public ProxyDog(Object target) {
  10. this.target = target;
  11. }
  12. /**
  13. * 给被代理对象生成代理对象
  14. *
  15. * @return
  16. */
  17. public Object getProxyInstance() {
  18. return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 被代理对象的类加载器
  19. target.getClass().getInterfaces(), // 被代理对象所实现的所有接口组成的数组
  20. new InvocationHandler() {
  21. /**
  22. * proxy 代理对象
  23. * method 被代理对象的方法
  24. * args 被代理对象的方法中的参数
  25. * 返回值 被代理对象的方法的返回值
  26. */
  27. @Override
  28. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  29. if ("call".equals(method.getName())) {// 对被代理的Dog类的call方法进行改造
  30. System.out.println("狗在嘻嘻嘻地笑");
  31. return null; //被代理对象的call方法没有返回值,所以这里返回null
  32. } else {//其他方法不改造,则调用method的invoke方法
  33. return method.invoke(target, args);//target-->被代理对象 args-->被代理对象的方法的参数
  34. }
  35. }
  36. });
  37. }
  38. }

测试一下:

  1. public class Test {
  2. public static void main(String[] args) {
  3. Animal dog = new Dog();//这里的dog是被代理对象
  4. //实例化装饰类
  5. // Animal zsDog = new ZhuangshiDog(dog);
  6. // zsDog.call();
  7. // zsDog.eat();
  8. Animal proxyDog = (Animal) new ProxyDog(dog).getProxyInstance();
  9. proxyDog.call();
  10. proxyDog.eat();
  11. }
  12. }

输出结果:

狗在嘻嘻嘻地笑

狗在嘚嘚的吃

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

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

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

  1. import java.io.PrintWriter;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. import java.sql.Connection;
  6. import java.sql.DriverManager;
  7. import java.sql.SQLException;
  8. import java.sql.SQLFeatureNotSupportedException;
  9. import java.util.LinkedList;
  10. import java.util.List;
  11. import java.util.logging.Logger;
  12. import javax.sql.DataSource;
  13. public class MyPool implements DataSource {
  14. // LinkList底层是链表,方便增删,不方便查找
  15. private static List<Connection> pool = new LinkedList<Connection>();
  16. static {
  17. try {
  18. // 注册mysql驱动
  19. Class.forName("com.mysql.jdbc.Driver");
  20. for (int i = 0; i < 5; i++) {// 向数据库连接池中创建5个连接
  21. // 创建连接
  22. Connection conn = DriverManager.getConnection("jdbc:mysql:///ccm", "root", "123456");
  23. pool.add(conn);
  24. }
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. throw new RuntimeException();
  28. }
  29. }
  30. @Override
  31. public Connection getConnection() throws SQLException {
  32. if (pool.size() == 0) {// 用户获取连接的时候,如果连接池中没有连接可用,则创建3条连接
  33. for (int i = 0; i < 3; i++) {
  34. Connection conn = DriverManager.getConnection("jdbc:mysql:///ccm", "root", "123456");
  35. pool.add(conn);
  36. }
  37. }
  38. Connection conn = pool.remove(0);// 从数据库连接池中取一个连接
  39. // 利用动态代理改造Connection类的close方法,这样我们只要调用Connection的close方法就会将连接还回到连接池中
  40. Connection proxyConn = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(),
  41. conn.getClass().getInterfaces(), new InvocationHandler() {
  42. @Override
  43. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  44. if ("close".equals(method.getName())) {
  45. // 对于想要改造的close方法,我们自己写
  46. returnConn(conn);
  47. return null;
  48. } else {
  49. // 对于不想改造的方法,调用被代理对象原来的方法
  50. return method.invoke(conn, args);
  51. }
  52. }
  53. });
  54. System.out.println("获取了一个连接,现在池里还有" + pool.size() + "个连接");
  55. return proxyConn;
  56. }
  57. /**
  58. * 将数据库连接还回到连接池中
  59. */
  60. protected void returnConn(Connection conn) {
  61. try {
  62. if (conn != null && !conn.isClosed()) {
  63. pool.add(conn);
  64. System.out.println("还回了一个连接,现在池里还有" + pool.size() + "个连接");
  65. }
  66. } catch (Exception e) {
  67. e.printStackTrace();
  68. }
  69. }
  70. @Override
  71. public PrintWriter getLogWriter() throws SQLException {
  72. // TODO Auto-generated method stub
  73. return null;
  74. }
  75. @Override
  76. public void setLogWriter(PrintWriter out) throws SQLException {
  77. // TODO Auto-generated method stub
  78. }
  79. @Override
  80. public void setLoginTimeout(int seconds) throws SQLException {
  81. // TODO Auto-generated method stub
  82. }
  83. @Override
  84. public int getLoginTimeout() throws SQLException {
  85. // TODO Auto-generated method stub
  86. return 0;
  87. }
  88. @Override
  89. public Logger getParentLogger() throws SQLFeatureNotSupportedException {
  90. // TODO Auto-generated method stub
  91. return null;
  92. }
  93. @Override
  94. public <T> T unwrap(Class<T> iface) throws SQLException {
  95. // TODO Auto-generated method stub
  96. return null;
  97. }
  98. @Override
  99. public boolean isWrapperFor(Class<?> iface) throws SQLException {
  100. // TODO Auto-generated method stub
  101. return false;
  102. }
  103. @Override
  104. public Connection getConnection(String username, String password) throws SQLException {
  105. // TODO Auto-generated method stub
  106. return null;
  107. }
  108. }

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

jdbc的实现类:

  1. import java.sql.Connection;
  2. import java.sql.PreparedStatement;
  3. import java.sql.ResultSet;
  4. public class JdbcTest {
  5. public static void main(String[] args) {
  6. Connection conn = null;
  7. PreparedStatement ps = null;
  8. ResultSet rs = null;
  9. MyPool pool = new MyPool();
  10. try{
  11. conn = pool.getConnection();
  12. ps = conn.prepareStatement("select * from users");
  13. rs = ps.executeQuery();
  14. while(rs.next()){
  15. String username = rs.getString("username");
  16. System.out.println(username);
  17. }
  18. }catch (Exception e) {
  19. e.printStackTrace();
  20. }finally{
  21. if(conn != null){
  22. try{
  23. conn.close();//调用close方法时,会将连接还回到连接池中,而不会将连接关闭
  24. }catch (Exception e) {
  25. e.printStackTrace();
  26. }finally{
  27. conn = null;
  28. }
  29. }
  30. if(ps != null){
  31. try{
  32. ps.close();
  33. }catch (Exception e) {
  34. e.printStackTrace();
  35. }finally{
  36. ps = null;
  37. }
  38. }
  39. if(rs != null){
  40. try{
  41. rs.close();
  42. }catch(Exception e){
  43. e.printStackTrace();
  44. }finally{
  45. rs = null;
  46. }
  47. }
  48. }
  49. }
  50. }

运行后的结果:

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

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

源码:点击打开链接

发表评论

表情:
评论列表 (有 0 条评论,294人围观)

还没有评论,来说两句吧...

相关阅读

    相关 代理模式java动态代理

    代理模式的作用及使用场景 使用代理模式的根本目的在于:如何在不直接操作对象的情况下,对此对象进行访问? 常用的场合包括:1)延迟加载;2)在调用实际对象的方法前后加入

    相关 装饰模式动态代理

    类的方法的增强的方式有很多,最初使用的继承,但继承的缺点是后期项目会产生很多的类,增加了项目的复杂度,于是,人们提出了组合,这点在Go语言设计最突出,Go语言甚至去掉了类的继承