Web程序员最常使用的数据库封装方式就是DAO,其实和马丁大爷在PoEAA中所说的表数据入口差不多:
01 class ArticleDAO
02 {
03 public function findById($id)
04 {
05 // SELECT * FROM article WHERE id = ...
06 }
07
08 public function findByTitle($title)
09 {
10 // SELECT * FROM article WHERE title LIKE ...
11 }
12 }
如上所示,是一个最简单的DAO例子,为了提高效率,很多时候需要把查询结果都缓存起来:
01 class ArticleCache
02 {
03 protected $cache;
04 protected $dao;
05
06 public function __construct($cache, $dao)
07 {
08 $this->cache = $cache;
09 $this->dao = $dao;
10 }
11
12 public function findById($id)
13 {
14 $key = 'id_' . $id;
15 if (!($result = $this->cache->get($key))) {
16 $result = $this->cache->findById($id);
17 $this->cache->set($key, $result);
18 }
19 return $result;
20 }
21
22 public function findByTitle($title)
23 {
24 $key = 'title_' . $title;
25 if (!($result = $this->cache->get($key))) {
26 $result = $this->cache->findByTitle($title);
27 $this->cache->set($key, $result);
28 }
29 return $result;
30 }
31 }
32
33 $memcache = new Memcache();
34 $memcache->connect('host', 'port');
35
36 $dao = new ArticleDAO();
37
38 $cache = new ArticleCache($memcache, $dao);
39
40 $cache->findById('id');
41 $cache->findByTitle('title');
解决问题时,面向对象编程不变的伎俩就是引入新层,上面这个缓存功能的实现亦是如此,阿基米德当年叫嚷着:给我一个支点,我可以撬动地球;面向对象的粉丝们也常有如此的豪言:加入一个新层,就可以实现任何功能。不过这里面出现了重复的坏味道,findById和findByTitle两个方法的实现代码大体上是重复的,继续重构:
01 class Cache
02 {
03 protected $cache;
04 protected $dao;
05
06 public function __construct($cache, $dao)
07 {
08 $this->cache = $cache;
09 $this->dao = $dao;
10 }
11
12 public function __call($name, $arguments)
13 {
14 if (strtolower(substr($name, 0, 4)) != 'find') {
15 return false;
16 }
17
18 $key = md5(strtolower(get_class($this->dao) . $name . serialize($arguments)));
19 if (!($result = $this->cache->get($key, $flags))) {
20 $result = call_user_func_array(array($this->dao, $name), $arguments);
21 $this->cache->set($key, $result);
22 }
23 return $result;
24 }
25 }
26
27 $memcache = new Memcache();
28 $memcache->connect('host', 'port');
29
30 $dao = new ArticleDAO();
31
32 $cache = new Cache($memcache, $dao);
33
34 $cache->findById('id');
35 $cache->findByTitle('title');
通过使用魔术方法__call,我们成功的去除了重复代码,而且这个Cache可以说是“万能”的,因为除了ArticleDAO外,我们可能还有CategoryDAO,CommentDAO等等,都可以用这个Cache来实现缓存。当然,Cache本身还不完善,有很多提升的余地,比如说缓存时间的设置,或者内容更新时如何通过观察者模式的方式来完成缓存的主动更新等等,这些问题我就不得瑟了,留给读者自己思考。