一、什么是AOP
AOP(Aspect Oriented Programming),面向切面编程。就是当我们在程序中执行一段逻辑时,在此逻辑前后,可以任意的进行其他操作的编程方式。在使用面向对象编程的时候,一般这个逻辑是某个模型的某个方法调用。它并不与OOP(Object Oriented Programming)冲突,实际上是相辅相成的。理论上,想实现AOP,应该需要很好的OOP基础。
二、AOP的好处
在解耦方面,可以使在不使用aop的情况下完全耦合的两个模块完全解耦。
在高效编程以及程序可维护性方面,不使用aop是代码混乱不堪,一个操作可能包含着其它的不相干的好几种操作。使用aop后,可使程序更加专注。能脱离业务逻辑写程序!
三、什么场景适用AOP
日志、权限、缓存、相关业务处理等等很多场景
四、从过程式到面向对象到面向切面
1. 过程式编程
<?php $fp = fopen('log', 'w'); if (!$fp){ exit('系统繁忙,请稍后重试'); } if (!mysql_connect('localhost', 'root', 'password')){ fwrite($fp, mysql_error()); fclose($fp); exit('系统繁忙,请稍后重试'); } if (!mysql_select_db('dbname')){ fwrite($fp, mysql_error()); fclose($fp); exit('系统繁忙,请稍后重试'); } $query = 'insert into article (`title`, `content`, `time`, `status`) values("第一篇文章", "内容", "2013-03-13 22:49:43", 0)'; if (mysql_query($query)){ echo '文章发表成功'; }else{ fwrite($fp, mysql_error()); fclose($fp); echo '文章发表失败'; }
2. 面向对象编程
class ArticleModel{ const STATUS_OK = 0; const STATUS_DEL = 1; public function __construct(){ if (!mysql_connect('localhost', 'root', 'password')){ $logModel = new LogModel(); $logModel->log(mysql_error()); throw new Exception('mysql connect error'); } if (!mysql_select_db('dbname')){ $logModel = new LogModel(); $logModel->log(mysql_error()); throw new Exception('mysql select db error'); } } public function add($title, $content, $time){ $query = 'insert into article (`title`, `content`, `time`, `status`) values("' . mysql_real_escape_string($title) . '", "' . mysql_real_escape_string($content) . '", "' . date('Y-m-d H:i:s', $time) . '", ' . self::STATUS_OK . ')'; if (($result = mysql_query($query)) !== false) { return $result; } $logModel = new LogModel(); $logModel->log(mysql_error()); return false; } } class LogModel { private $fp; public function __construct(){ $this->fp = fopen('log', 'w'); if ($this->fp === false) { throw new Exception('open log file failed'); } } public function log($message){ fwrite($this->fp, $message); } public function __destruct(){ $this->fp && fclose($this->fp); } } $articleModel = new ArticleModel(); if ($articleModel->add()){ echo '文章发表成功'; }else{ echo '文章发表失败'; }
3. 面向切面编程
<?php class proxy{ private $config; private $object; public function __construct($object){ $this->object = $object; $this->config = include 'aopConfig.conf.php'; } public function __call($method, $args){ $classname = get_class($this->object); $aopconfig = array(); foreach ($this->config as $key => $value) { if ("{$classname}::{$method}_before" == $key) { $aopconfig['before'][] = $value; } if ("{$classname}::{$method}_after" == $key) { $aopconfig['after'][] = $value; } } foreach ($aopconfig['before'] as $config) { call_user_func_array($config['callable'], array('object' => $this->object, 'method' => $method, 'args' => $args)); } $result = call_user_func_array(array($this->object, $method), $args); foreach ($aopconfig['after'] as $config) { call_user_func_array($config['callable'], array('object' => $this->object, 'method' => $method, 'args' => $args, 'result' => $result)); } return $result; } } class mysql{ public function connect($host, $user, $password){ return mysql_connect($host, $user, $password); } public function usedb($dbname){ return mysql_select_db($dbname); } public function query($sql){ return mysql_query($sql); } } class ArticleModel{ const STATUS_OK = 0; const STATUS_DEL = 1; public function __construct(){ $this->mysql = new proxy(new mysql()); //使用代理对象包装目标对象, 为目标对象提供切面支持 $this->mysql->connect('localhost', 'root', 'password'); $this->mysql->usedb('dbname'); } public function add($title, $content, $time){ $query = 'insert into article (`title`, `content`, `time`, `status`) values("' . mysql_real_escape_string($title) . '", "' . mysql_real_escape_string($content) . '", "' . date('Y-m-d H:i:s', $time) . '", ' . self::STATUS_OK . ')'; if (($result = $this->mysql->query($query)) !== false) { return $result; } return false; } } class LogModel { private $fp; public function __construct(){ $this->fp = fopen('log', 'w'); if ($this->fp === false) { throw new Exception('open log file failed'); } } public function log($message){ fwrite($this->fp, $message); } public function __destruct(){ $this->fp && fclose($this->fp); } } $articleModel = new proxy(new ArticleModel()); if ($articleModel->add()){ echo '文章发表成功'; }else{ echo '文章发表失败'; }
注意看代码中的各模块之间的耦合程度。然后,如果你发现我没有记录日志,那就继续看下边的这个配置文件吧~
<?php return array( 'mysql::connect_after' => array( 'callable' => array('log', 'mysqlConnectError'), ), 'mysql::usedb_after' => array( 'callable' => array('log', 'mysqlUserdbError'), ), 'mysql::query_after' => array( 'callable' => array('log', 'mysqlQueryError'), ), 'ArticleModel::add_after' => array( 'callable' => array('log', 'articleAdd'), ), );
有了配置文件,就为AOP提供了可编程入口了。具体的针对每个切面的编程的实现如下
<?php class log{ private static function getfp(){ static $fp = null; if ($fp === null){ $fp = fopen('log', 'w'); } return $fp; } private static function write($message){ $fp = self::getfp(); return fwrite($fp, $message); } public static function mysqlConnectError($args){ self::write("mysql connect error. host:{$args['args'][0]} user:{$args['args'][1]} password:{$args['args'][2]}"); } public static function mysqlUserdbError($args){ self::write("mysql select db failed. dbname:{$args['args'][0]}") } public static function mysqlQueryError($args){ self::write("mysql query failed. sql:{$args['args'][0]}"); } public static function articleAdd($args){ if ($args['result'] === false){ self::write("article add failed. title:{$args['args'][0]} content:{$args['args'][1]} time:{$args['args'][2]}"); } } }
到此,可以总结一下三种方法的优缺点了。
面向过程:过程式编写代码,新增需求就去相应位置添加新逻辑代码,可能添加很多处(例如凡是错误都要记录日志)
面向对象:封装了数据对象,但当需求增加的时候,或是在对象内,或是在业务主逻辑处,需要增加相应新逻辑代码。很容易出现高耦合的情况。如果多处调用,同样需要添加多处逻辑。
面向切面:为对象提供切面编程支持,全面解耦。主业务逻辑相当专一,无需细想在文章插入后的后续操作。比如增加用户文章计数,同样可以用AOP的方式实现,并且实现的更加完美。