首页 > 技术文章 > 设计模式:装饰器模式实现 固定类功能

gosteps 2018-02-05 17:39 原文

一年半前写了一篇文章Spring3:AOP,是当时学习如何使用Spring AOP的时候写的,比较基础。这篇文章最后的推荐以及回复认为我写的对大家有帮助的评论有很多,但是现在从我个人的角度来看,这篇文章写得并不好,甚至可以说是没有太多实质性的内容,因此这些推荐和评论让我觉得受之有愧。

基于以上原因,更新一篇文章,从最基础的原始代码–>使用设计模式(装饰器模式与代理)–>使用AOP三个层次来讲解一下为什么我们要使用AOP,希望这篇文章可以对网友朋友们有益。

原始代码的写法

既然要通过代码来演示,那必须要有例子,这里我的例子为:

1
有一个接口Dao有insert、delete、update三个方法,在insert与update被调用的前后,打印调用前的毫秒数与调用后的毫秒数

首先定义一个Dao接口:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
*/
public interface Dao {
 
    public void insert();
   
    public void delete();
   
    public void update();
   
}

然后定义一个实现类DaoImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
*/
public class DaoImpl implements Dao {
 
    @Override
    public void insert() {
        System.out.println("DaoImpl.insert()");
    }
 
    @Override
    public void delete() {
        System.out.println("DaoImpl.delete()");
    }
 
    @Override
    public void update() {
        System.out.println("DaoImpl.update()");
    }
   
}

最原始的写法,我要在调用insert()与update()方法前后分别打印时间,就只能定义一个新的类包一层,在调用insert()方法与update()方法前后分别处理一下,新的类我命名为ServiceImpl,其实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
*/
public class ServiceImpl {
 
    private Dao dao = new DaoImpl();
   
    public void insert() {
        System.out.println("insert()方法开始时间:" + System.currentTimeMillis());
        dao.insert();
        System.out.println("insert()方法结束时间:" + System.currentTimeMillis());
    }
   
    public void delete() {
        dao.delete();
    }
   
    public void update() {
        System.out.println("update()方法开始时间:" + System.currentTimeMillis());
        dao.update();
        System.out.println("update()方法结束时间:" + System.currentTimeMillis());
    }
   
}

这是最原始的写法,这种写法的缺点也是一目了然:

  • 方法调用前后输出时间的逻辑无法复用,如果有别的地方要增加这段逻辑就得再写一遍
  • 如果Dao有其它实现类,那么必须新增一个类去包装该实现类,这将导致类数量不断膨胀

使用装饰器模式

接着我们使用上设计模式,先用装饰器模式,看看能解决多少问题。装饰器模式的核心就是实现Dao接口并持有Dao接口的引用,我将新增的类命名为LogDao,其实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
*/
public class LogDao implements Dao {
 
    private Dao dao;
   
    public LogDao(Dao dao) {
        this.dao = dao;
    }
 
    @Override
    public void insert() {
        System.out.println("insert()方法开始时间:" + System.currentTimeMillis());
        dao.insert();
        System.out.println("insert()方法结束时间:" + System.currentTimeMillis());
    }
 
    @Override
    public void delete() {
        dao.delete();
    }
 
    @Override
    public void update() {
        System.out.println("update()方法开始时间:" + System.currentTimeMillis());
        dao.update();
        System.out.println("update()方法结束时间:" + System.currentTimeMillis());
    }
 
}

在使用的时候,可以使用”Dao dao = new LogDao(new DaoImpl())”的方式,这种方式的优点为:

  • 透明,对调用方来说,它只知道Dao,而不知道加上了日志功能
  • 类不会无限膨胀,如果Dao的其它实现类需要输出日志,只需要向LogDao的构造函数中传入不同的Dao实现类即可

不过这种方式同样有明显的缺点,缺点为:

  • 输出日志的逻辑还是无法复用
  • 输出日志的逻辑与代码有耦合,如果我要对delete()方法前后同样输出时间,需要修改LogDao

但是,这种做法相比最原始的代码写法,已经有了很大的改进。

推荐阅读